				   MIRA SOFTWARE

				 MODULA-2 COMPILER
				For the ZX Spectrum

 			    (c) Mira Software 1991

0:Contents ................................................. 1
1:Using the editor and compiler ............................ 2
2:A short Modula-2 tutorial ................................ 6
	Introduction ......................................... 6
	Assignment and data types ............................ 8
	The libraries of procedures .......................... 10
	Control structures ................................... 11
	Writing your own procedures .......................... 14
	Planning your programs ............................... 16
	Arrays and subrange types ............................ 17
	Records and the WITH statement ....................... 19
	Enumerated types and variant records ................. 20
	Set Types ............................................ 21
	Pointer types and recursion .......................... 22
	Modules and procedure types .......................... 23
	Concurrent Programming ............................... 25
3:Summary of the implementation ............................ 28
	Compiler Directives and how the compiler works ....... 30
4:Error Messages ........................................... 32
5:The library of modules ................................... 34
	STDIO - Standard input and output .................... 35
	DRIVES - Loading, Saving, etc. ....................... 37
	FASTO - Fast screen output ........................... 39
	MAINREAD - Input from upper screen ................... 39
	STRINGS - Character string manipulation .............. 41
	PLUS3IO - Files on the +3 ............................ 42
	DATA - Searching and sorting ......................... 43
	HARDWARE ............................................. 44
	BASIC ................................................ 45
	ERRORS ............................................... 46
	SETOPS - Set operators for large sets ................ 46
	STORAGE - Allocation for pointer variables ........... 47

					1

	GRAPH0 ............................................... 48
	GRAPH1 - Line drawing ................................ 48
	GRAPH2 - Circles and ellipses ........................ 49
	GRAPH3 - Arcs ........................................ 49
	CLIPPING ............................................. 50
	FILL - Contour filling ............................... 50
	SYMBOL - Sprite-like symbols ......................... 51
	NUMIO - Simple numeric input & output ................ 52
	R2IO \ ............................................... 53
	R3IO | - Input & output for real numbers ............. 53
	R5IO / ............................................... 53
	R2MATH \ ............................................. 54
	R3MATH | - Mathematical functions .................... 54
	R5MATH / ............................................. 55
6:General information ...................................... 56


				1:USING THE COMPILER

	To start using the compiler you should first follow the
instructions on the Installation sheet, to load the compiler and to
transfer any modules which are required from tape to drive. You will
arrive at the request "Enter name of program". If you want to load a
module at this stage then enter its name, otherwise press the down
key (CAPS SHIFT & 6). The message "STOP in INPUT" will appear
and the compiler will be in control key mode; i.e. pressing keys will
perform given actions.

				Entering Lines
	Pressing ENTER will put the compiler into line entry mode which
allows you to type in the program as a series of lines. The program
is listed on the screen as the lines are entered, with a cursor
(flashing '>') marking the current line. Note that Modula-2 is not a
line based language so that ends of lines do not necessarily
separate statements. Lines consisting of no characters will not be
entered, so to produce a blank line you should enter a line
consisting of a space. In order to leave line entry mode and go into
control key mode you should press the down key.

				CONTROL KEYS
	The cursor may be moved up or down a line by pressing the up
or down keys (CAPS SHIFT & 7 or CAPS SHIFT & 6). Pressing 7 or 6
will move the cursor up or down eight lines at a time. SYMBOL

					2

SHIFT & 7 will move the cursor to the start of the program and
SYMBOL SHIFT & 6 will move it to the end. The current line may be
edited by pressing 1 or EDIT. Pressing E allows the entry of a line
before the current line. (Entering line entry mode by pressing ENTER
will add lines after the current line). Pressing H gives a list of control
keys and their functions on the screen.

			Moving Blocks of lines
	It is possible to move a block of lines within the program. To do
this you should move the cursor to the start of the block and press B
to mark it, then move the cursor to the end of the block, and press K
to store the block (Cut). The block may be later retrieved at the
position of the cursor by pressing P (Paste). Pressing P again will
then produce further copies of the block that has been stored. Note
that what is in the paste buffer may be erased by some disc drive
operations. A block of lines may be deleted by marking it as above
and pressing CAPS SHIFT & 0 (DELETE). Pressing V will list the
marked block on stream 3 (i.e. a printer). If no block is marked then
the whole program will be printed.

			Deleting the whole program
	Note that the whole program may be deleted by pressing
SYMBOL SHIFT & 7, B, SYMBOL SHIFT & 6, then CAPS SHIFT & 0.

				The Options byte
	Pressing the letter O allows entry of a number which is the
options byte. This specifies what is to be used for storage and
loading of programs. 0 represents tape storage, 13 represents the
ramdisc, and numbers between these give the corresponding drive.
15 specifies that loading will first try the ramdisc, then drive 1, and
saving will be to both drive 1 and ramdisc. In the tape-only versions
drive 1 refers to the tape but with messages sent to the printer
indicating what action is required. Adding 16 to the number specifies
that a line feed ( CHR$(10) ) is to be added to the end of lines when
saved in a file or printed out. This will allow microdrive files to be
edited by word processors such as Tasword 3. (When a file is
loaded into the compiler any line feeds are removed). Adding 32 to
the number sets the compiler to search for errors in the program
rather than produce any code. (See Section 4). Adding 64 instructs
the compiler to save the compiled code and symbol table at the end
of compilation. The code is saved with the name of the program
preceded by a '#', to the drive specified by the options byte. (See
section 3 for further details).

					3

			Saving and loading programs
	To save a program you should press S and give the name of
the program as required. Just pressing ENTER will use the name
given in the module heading or COPY SOURCE if no name is found
( SOURCE for Wafadrive). If the program already exists on the
drive then you will be asked for confirmation that it should be
overwritten.
	To load a program press L and give the name of the program
(pressing ENTER will give a catalogue of the drive, or load the next
program if tape is used). If this is done with a program already
present then the new program will be merged into the old one at the
position of the cursor. If you do not want the programs to be
merged then press SYMBOL SHIFT & L instead. This will cause the
old program to be saved and then removed from memory before the
new one is loaded. (Note 1: The old program is only saved if it has
been altered since it was loaded. It is saved to ramdisc if available,
otherwise to drive 1 - this is irrespective of the options byte. It is not
saved in the tape only version.)

	Programs are stored in the following ways:
Ramdisc ...... Basic-type program	Disciple ...... Code
Tape ......... Basic-type program	Microdrive .... File
Beta ......... Basic-type program	Opus .......... File
Wafadrive .... Basic-type program	Plus 3 ........ Code


			Exiting from the compiler
	Pressing STOP (SYMBOL SHIFT & A) will save the current
program as in Note 1, then perform a NEW command, allowing a
return to Basic. The compiler will still be in memory, and can be
re-entered at the address given on the installation sheet.


				Finding words
	Pressing F allows you to enter a word to be found in the
program. This always starts looking from the beginning of the
program. Pressing N searches for the next occurrence of the word. If
the word is found then it is marked with a flashing '?'.

				Compiling programs
	When a program has been entered into the compiler it may be
compiled by pressing the X key. Other modules will be loaded from
ramdisc or drive 1 when required. (The current program will be

					4

saved as in Note 1.) The compilation takes place in several passes
of the program, and sometimes the same module will need to be
loaded more than once.
	If a mistake in the Modula-2 program is detected during the
compilation an error message will be displayed with a flashing '?' at
the position in the program where the mistake was detected, and the
cursor at the line containing it, ready for the line to be edited and the
mistake corrected.
	While the compilation is in progress the table of identifier
names etc. will be stored in screen memory, resulting in a random pattern
appearing on the screen. (This is why messages have to be sent to
the printer in the tape-only version.) When the compilation has been
successfully completed the words CLEAR and SAVE followed by
numbers will be printed on the screen. Pressing any key will
perform a NEW command, with the compiled code stored above
RAMTOP. The compiler will still be in memory, and can be
re-entered using the entry point given on the installation sheet.
	The compiled program may be run by using RANDOMIZE USR
48500. This may be within a Basic program if required. If an error
occurs in the running of the Modula-2 program then an error
message will be displayed. If the error occured in a ROM routine
then this will cause an exit from the program and a normal Spectrum
error message will be given with the number of the Basic line which
called the Modula-2 code. If it is in the Modula-2 run-time
routines, then an appropriate error message is given, and the user
will be given the option of continuing the program. However, doing
so may well cause a crash, so you are recommended to exit at this
point, when the error message "BREAK into program" will be given.
The position of the error in the Modula-2 code can be found by
returning to the compiler, selecting option 32 and pressing X.
	The start of the compiled code is given by the number following
SAVE, and the end is at 49152. This is the code which will be saved
using option 64. On reloading you should first set RAMTOP by
clearing to the number following CLEAR (less if dynamic variables
are used, as described later.)

					5

			2:A short Modula-2 tutorial
	This section is meant to assist you in learning how to program
using the compiler. Some previous experience of programming
would be useful. It is not designed as a programming course, for
which you should choose a book to your own taste. The modules
Terminal, InOut, RealInOut, MathLib0, LineDrawing and Processes
are included with this compiler for compatibility with textbooks on
Modula-2. However, it is recommended that you use the modules
described in section 5 instead as soon as you understand how to do
so.
	Note that very few examples are given here, and it is
recommended that when a keyword is given in the text, you should
find it in the example programs (using the F key), to see how it is
used. The example programs are CLOCK, ROUTE, STARBASE, and
SYMDES, with subsidiary modules VILLAGES and RADECSTG. You
will be able to load all of these into the ramdisc if you have a
Spectrum with 128K.

				Introduction
	If you have programmed in Basic you will realise that numbers
and character strings are two different types of data, and operations
which apply to one do not apply to the other; e.g. you cannot add a
number to a string. In other languages such as Pascal this idea of
having different types of data is taken further with numbers being
separated into the INTEGER type of whole numbers, and the REAL
type of numbers which may have a decimal part. The CHAR type
consists of characters, and the BOOLEAN type has the values TRUE
and FALSE. Modula-2 also has the CARDINAL type of positive
whole numbers, and several other types defined. As well as these
you will see later that you can define new data types. All this means
that it is vital for the programmer to be aware of the type of every
item of data used in a program. This strictness of the language
makes it harder to learn than a language such as Basic, but means
that programs can be made more reliable and efficient.
	Note that unlike many other languages, in Modula-2 capital and
lower case letters are considered distinct, so that when a variable or
other identifier is used, it has to match its declaration exactly. All of
the Modula-2 keywords have to be in capitals. In this implementation
the names of procedures from the library modules are also in
capitals.
	The following is an example program for you to consider.

					6
	MODULE addup;
	VAR a,b,sum:CARDINAL
	BEGIN
	WRITELN('Enter first number');
	READLN(a);
	WRITELN('Enter second number');
	READLN(b);
	sum:=a+b;
	WRITELN('The sum is',sum)
	END addup.

	From this you can see how a Modula-2 program is put together.
It starts with the word MODULE followed by the name of the
program and a semicolon. Then come sections declaring what is
going to be used in the program, each ending with a semicolon. In
this case there is only one such section, starting with VAR indicating
that variables are being declared, their names being a, b and sum
and they are all of type CARDINAL. BEGIN indicates that the
declarations are finished and this marks the start of the statements of
what is going to be done by the program. These statements are
separated by semicolons. The end of the program is indicated by
the word END followed by the name of the module and a full stop.
Note that END is not a statement (it does not indicate any action to
be performed) and so does not need a semicolon to separate it from
the statement before it.
	You will probably know that a variable is a place to store data,
but the type CARDINAL may be unfamiliar to you. This type is
composed of the positive whole numbers. Thus the variable
declaration indicates that the variables a, b and sum may only store
such values.
	The procedure WRITELN prints items on a line on the screen.
The quote marks indicate a string of text, so that whatever is inside
them is printed out as written. The second item in the last WRITELN
statement is not in quotes, and so is not printed out as the word
sum, but as the value stored in the variable called sum. The
procedure READLN gets a value typed in at the keyboard, and puts
it in in the specified variable. The statement sum:=a+b adds together
the values in the variables a and b and puts the result in the
variable sum . So the given program prompts the user to enter two
values at the keyboard, then adds them together and prints the
result.
	Now that the working of this program has been described, you

					7

should type it in for yourself, but without looking at it in the manual.
Try to do this by remembering how the program works, rather than
by memorizing it. When you have typed it in, make sure that you
can compile and run it without any errors.
	Thus the form of a Modula-2 program is as follows;
	MODULE mname;
	declarations;
	BEGIN
	statements;
	END mname.
	(Note: Here, and in following definitions of parts of Modula-2
programs, words in BOLD text are Modula-2 keywords; i.e. they are
actually typed in the program. Other words are descriptions of what
occurs in a given position.)
	The program starts running after the BEGIN keyword, and
executes the statements in the order given - fairly obvious but
surprisingly often forgotten. When you are writing a program it is
useful for you to be able to imagine that you are the computer, and
work through what the program does by hand.


		Assignment, simple data types and operators
	The statement sum:=a+b in the previous example is called an
assignment statement. In general this will take the form:
	variable:=expression
and will work out the expression on the right of the := and put it
into the variable on the left. The expression and the variable should
be of the same type, or else their types should be compatible.
Rules for when two different types are compatible are given at the
appropriate places in this manual. Note that any variable used in a
Modula-2 program must be declared following the VAR keyword, in
the declarations part of the program.
	VAR variable1, variable2, ...:type;
	Variable names must start with a letter, which may be followed
by further letters and digits. Spaces and symbols may not be part of
variable names.
	We now take a closer look at some of the data types of
Modula-2 and the operations which apply to them.
	INTEGER, includes positive and negative whole numbers (within
the limits -32768 to 32767); e.g. 10, -1234, 30000. The
operators +, -, *, DIV and MOD, apply to INTEGER numbers. You
probably know that * represents multiplication. For two integers p

					8

and q, p DIV q is the integer part of p divided by q, and p MOD
q is the remainder.
	CARDINAL includes positive whole numbers in the range 0 to
65535; e.g. 23, 49152. CARDINAL constants may also be written as
hexadecimal numbers followed by H or as octal numbers followed
by B; e.g. 36B, 0C000H. All numbers must start with a digit, so a
leading zero may be needed for the hexadecimal representation.
The operators +, -, *, DIV and MOD apply to CARDINAL numbers.
	REAL includes numbers which may have a decimal part. REAL
constants put in a program must have a decimal point and at least
one digit on each side of it. They may also have the letter E
followed by a number, indicating multiplication by a power of 10;
 e.g. -3.14159, 1.23E6, -2.0E-7. REAL numbers are stored in 5
bytes with a 1 byte exponent and 4 byte mantissa. The operations
on them are those from the Spectrum ROM. This compiler also
implements numbers with a 1 byte exponent and 1 and 2 byte
mantissae called REAL2 and REAL3 respectively. These are not as
accurate as REAL numbers, but give much quicker calculations.
Note that REAL may also be called REAL5. The operators +, -, *, /
apply to numbers of all three real types (/ represents division).
	Different numeric types may not be mixed in an expression. For
instance, if n is an INTEGER variable and x and y are REAL
variables then the statement y:=n*x is not allowed. Functions are
provided to convert numbers to a given numeric type from any other
numeric type. These are called INT, CARD, RL5, RL3 and RL2
respectively. Thus the above statement should be y:=RL5(n)*x.
These functions are the only way to put constants of type REAL2 or
REAL3 into a program; e.g. RL3(3.1416), RL2(60).
	The type CHAR includes letters and the rest of the Spectrum
character set. (Note that variables of type CHAR can only store a
single character. You will see how to deal with strings of characters
later). Constants of type CHAR consist of a character in single or
double quotes. They may also be given as the ASCII code of the
character in octal followed by the letter C or in hexadecimal
followed by the letter I. The function CHR is used to convert a
CARDINAL expression into the corresponding character in the ASCII
sequence. The function ORD does the reverse, giving the ASCII
code, of type CARDINAL, for a value of type CHAR. The following
expressions all represent the same value of type CHAR: 'A', "A",
CHR(65), 41I, 101C.
	Expressions of the above types may be compared using the
relational operators, which are: <, >, <=, >=, =, <> . The result

					9

of these comparisons is a value of type BOOLEAN. This type has
just two values, TRUE and FALSE. BOOLEAN expressions find their
main use in control structures, such as IF statements, which we shall
come to later. However, just as with any other type, it is possible to
declare variables of type BOOLEAN, and assign BOOLEAN expressions
to them. The operators whcih apply to BOOLEAN expressions
are AND, OR and NOT. Usually the correct usage of these
operators is obvious from what would be said in English, but
sometimes some thought is needed. Whereas in English you might
say "If the variable x is not equal to 1 or 2", the appropriate
Modula-2 expression would not be x <> (1 OR 2), nor
(x<>1) OR (x<>2). It should actually be (x<>1) AND (x<>2). The
expression NOT((x=1) OR (x=2)) would also be correct.
	The types CARDINAL, INTEGER, CHAR and BOOLEAN are
known as ordinal types. An ordinal type consists of a sequence of
values, so that given a value of that type it is possible to say what
the following one is (unless the value is the last in the sequence).
For example 3 is followed by 4, 'K' is followed by 'L'. The
procedures INC and DEC apply to ordinal variables to give the next or
previous value. For instance, if x is a CARDINAL variable then
INC(x) does the same as x:=x+1. The functions MAX and MIN give
the maximum and minimum values of an ordinal type; e.g.
MAX(CHAR) is CHR(255). Note that the real types are not ordinal
types, since, for instance, it is not possible to say what the next
REAL number after 1.6 is.
	It is possible to give names to constants, using a CONST
declaration, in the declarations part of the program; e.g.
	CONST	pi=3.1415;
		SIXTY=RL3(60);
		maxcount=100;
	Giving constants names is useful for making your programs
more readable and easier to maintain. It may also mean that less
memory is used, as the value only needs to be stored once.

			The libraries of procedures
	In the above example you saw the use of the built-in
procedures READLN and WRITELN. In fact this involved a bit of
cheating as these procedures do not really belong to Modula-2, but
are simplified forms of Pascal procedures, and are implemented in
this compiler to give you a simple method of input and output. The
idea of Modula-2 is not to have many procedures built into the
compiler itself, but to be able to get them from a library of separate

					10

modules. The installation sheet tells you how to transfer these
modules from tape to a drive.
	To be able to use a procedure from a library module, this must
be declared in an import list as follows:
	FROM module IMPORT procedure1,procedure2 ...;
(Note: The 3 dots ... mean that a list of items may appear in your program. This is pointed out to
distinguish it from the token consisting of 2 dots .. which is part of the Modula-2 language.)
	Import lists should be positioned in your program after the
module heading, and before any declarations.
	A procedure is called simply by putting its name in your
program. Most procedures require parameters, that is items of data
to act upon. These should be given in brackets after the procedure
name. For example, the procedure SETATTRS from the module
GRAPH0 is used to set a number of the attribute squares on the
screen to a given attribute. This requires the x and y position of the
first attribute square to be set, the number of squares to be set, and
the attribute they are to be set to. The parameters must be in the
order specified. So SETATTRS(0,10,128,32) would set the middle 4
lines of the screen to be green paper with black ink. Somewhere
else in your program you might want to clear the graphics screen
using the procedure GCLS, from the same module. This is a
procedure which does not require parameters, so the statement
calling it would just be the word GCLS, without brackets following it.
Note that to use these two procedures in a program the import list:
	FROM GRAPH0 IMPORT SETATTRS,GCLS;
would have to appear at the start of your program.
	A function is a kind of procedure which is not used as a
statement on its own, but to return a value as part of an expression.
Examples of functions are SIN and COS from the module R5MATH.
	The procedures available from each module, and the number
and types of parameters which each requires, can be seen from the
definition part of the module. These are given in section 5 of this
manual. Further details of what these procedure headings mean are
given in the section on writing your own procedures.
	The module SYSTEM is built into the compiler, rather than being
loaded in, and deals with certain system-dependent facilities.

			Control structures
	The first control structure we deal with will be familiar to Basic
programmers, it is the IF statement.
	IF condition THEN statements END
	Here the statements following THEN are only executed if the
condition is TRUE. The condition has to be a BOOLEAN expression.

					11

	The IF statement is written on one line here, but usually the
statements in the IF statement would be written on separate lines.
Remember though that it is not putting statements on separate lines
which separates them, but putting semicolons in between them.
These statements may be assignments, procedure calls, but will
often be other control structures. In particular IF statements may be
nested, one inside another.
	IF statements may also contain ELSE.
	IF condition THEN Statements ELSE Statements END
	If the condition is true then the statements following the THEN
are executed, if not then the statements following the ELSE are
executed. Note that when IF statements are nested, the fact that
each has to have an END means that there is no ambiguity about
which one an ELSE may belong to.
	Sometimes it is useful to be able to select from several different
alternatives. To avoid the need to have more than one IF statement,
ELSIF can be used.
	IF condition THEN Statements
	ELSIF condition THEN Statements
	ELSIF condition THEN Statements
	.
	.
	ELSE Statements
	END
	The program goes through the conditions until one is found
which is TRUE, then the corresponding statements are executed.
None of the other statements are executed, even if later conditions
are also TRUE. If all the conditions are FALSE then the statements
following the ELSE are executed (if the ELSE part is present).
	Often the selection of different alternatives depends upon the
value of an ordinal expression. Instead of having an IF & ELSIF
statement with the same expression in each comparison the CASE
statement is available.
	CASE expression OF
	const1:Statements|
	const2:Statements|
	.
	.
	constn:Statements
	ELSE Statements
	END

					12

	(The | character which separates the cases is obtained by
shifted S in extended mode on the Spectrum.)
	Here the statements are executed which follow the constant
equal to the value of the expression. If none of them are then the
statements following the ELSE are executed (if present). Instead of
just a single constant at the start, a case may have a range
consta..constb or a list of constants (or ranges) separated by
commas. The corresponding statements will be executed if the
expression is equal to any of the constants in the list or within the
range.
	The above statements deal with choosing between several
alternative actions. Another important aspect of programming is
repeating actions. In Modula-2 the statements concerned with this are
the FOR, WHILE, REPEAT and LOOP statements.
	The FOR statement will be familiar to Basic programmers, but
there is no NEXT statement, the end of the repeated part being
marked by END.
	FOR variable:=expression1 TO expression2 DO Statements END
	The first expression will be assigned to the variable and then the
statements executed once for each value of up to the second
expression. If the second expression is less than the first then the
statements will not be executed. The types of the expressions must
be compatible with that of the variable, which must be an ordinal
type. The FOR statement may have a constant step value after the
word BY.
	FOR variable:=expression1 TO expression2 BY ConstExpr DO
	Statements END
	The REPEAT loop is as follows:
	REPEAT Statements UNTIL condition
	This should be fairly clear; the statements are repeatedly
executed until the condition is satisfied. Note that the condition is
checked at the end of the statements so they will be executed at
least once. Alternatively, using the WHILE statement, the condition
is checked at the start.
	WHILE condition DO Statements END
	Here the condition is checked and if it is satisfied then the
statements are executed. This is repeated until the condition is not
satisfied.
	Note that if the condition in a REPEAT loop becomes satisfied in
one of the statements, the rest of the statements will still be
executed before it is checked, and similarly with the WHILE loop. To
be able to exit from a loop from within the statements, a more

					13

general construct is required. This is the LOOP statement
	LOOP Statements END
	As it stands this will be an infinite loop, and the statements will
be repeated indefinitely. The programmer must put in explicitly
where an exit from the loop is required, using an EXIT statement
which is just the word EXIT. Usually this will be in an IF statement,
allowing conditional exit from the loop.
	You have now seen the assignment statement, procedure calls,
and the IF, CASE, FOR, REPEAT, WHILE, LOOP and EXIT
statements. These are all of the statements of Modula-2 except for
two (the WITH and RETURN statements).
	Note that there is no GOTO statement in Modula-2. A
backwards jumping GOTO means some sort of loop, and a forward
GOTO is either used to jump out of a loop or to miss out statements
depending on a given condition. Modula-2 has control structures to
deal with each of these cases so GOTO is not required.

			Writing your own procedures
	You have seen how to call procedures from the library modules;
now you will see how to write such procedures in your own
programs. The form of a procedure definition is much the same as
that of the whole program.
	PROCEDURE name;
	declarations
	BEGIN
	Statements
	END;
	The procedure definition goes in the declaration section of the
program. The procedure is called simply by putting its name as a
statement in your program.
	The declaration section of the procedure may contain VAR and
CONST declarations. These are then local to the procedure; i.e. the
variables cannot be referred to outside the procedure, although the
procedure may refer to variables in the main program. In the same
way the procedure may contain other procedure declarations.
	A procedure call is like a GOSUB in Basic, since the position in
the program when is remembered, for resumption when the
procedure ends. Do not make the mistake of thinking of a
procedure call as a GOTO the procedure start.
	To be able to pass parameters to a procedure, they have to be
declared in brackets directly after the procedure name.
	PROCEDURE name(param,...:typename;...);

					14

	The parameters declared here are called formal parameters and
within the procedure they behave as variables containing the values
passed to the procedure. To pass these values from the program
when the procedure is called they must be put in brackets after the
procedure name.
	name(expression...);
	These values are called actual parameters. Note that for each
formal parameter, there has to be a corresponding actual parameter
of compatible type. The actual parameters may be constants,
variables or an expression which will be worked out to find the value
passed. This type of parameter is called a value parameter, as the
only information passed to the procedure is the value of the
expression. There is another sort of parameter where the actual
parameter must be a variable. This is called a variable parameter
and is indicated by putting the keyword VAR before the formal
parameter.
	PROCEDURE name(VAR param,...: typename;...);
	Within the procedure the formal parameter represents the
variable passed as the actual parameter. In particular if the formal
parameter has a value assigned to it, then it will actually be assigned
to the actual parameter. This means that variable parameters may
be used to return values from a procedure. The type of the actual
parameter has to be the same as that of the formal parameter rather
than just compatible (with a few exceptions). Be careful to
distinguish between the VAR occurring here, and that in variable
declarations.
	If a formal parameter has type WORD then the actual parameter
may be of any type that requires one byte of storage space. The
type WORD must be imported from the SYSTEM module, and
represents a single byte of storage. In order to manipulate an item
of type WORD it may be necessary to convert it to another type by
means of a typecast, which interprets an expression of one type as
having another type. A typecast takes the form typename(expression).
The expression must require the same number of bytes of
storage as the type it is converted to. For a variable v of a type T,
the number of bytes of storage needed is given by SIZE(v), or by
TSIZE(T). The function TSIZE must be imported from the SYSTEM
module.
	Although variable parameters provide a way of returning values
from procedures, another method, when only one value is to be
returned, is to use a function procedure. This returns a value rather
than being a statement in its own right. This may make your

					15

program neater; for instance, it is possible to write WRITELN(SIN(x))
whereas with a variable parameter it would need to be something
like SINCALC(x,y);WRITELN(y). The heading of a function is as
follows:
	PROCEDURE name(param,...:typename1...):typename2;
	Here typename2 is the type of the parameter returned by the
function. Within the statements making up the body of the function
there must be a return statement, which takes the form:
	RETURN expression
	The value of the expression is then returned by the function.
Procedures which are not functions may contain the RETURN
statement, without an expression following, to give a return to the
calling program before the end of the procedure.
	Functions are usually thought of in a mathematical context, and
so have a numeric type, but they may have other ordinal types, such
as BOOLEAN.
	If you look at the procedures for inputting numbers from the
module NUMIO, you will see that they are defined as functions
without parameters, so instead of putting RDCARD(x) you need to
put x:=RDCARD(). This means that the type of x only needs to be
compatible with CARDINAL. Note that functions without parameters
still require the brackets to be present, whereas normal procedures
without parameters would not have brackets.

				Planning your programs
	Procedures are a very important part of good programming
practice. They allow you to divide your program up into parts which
do not interact with each other except in a well defined way. Then
you only have to deal with one part of your program at a time.
Giving your programs this sort of heirarchical structure is part of
what is called structured programming.
	There remains the question of the order in which this hierarchy
is put together. One way, called top-down programming, is to write
the main program first, putting in procedure calls without having
written the procedures. This means that when you come to write the
procedures you have a clear idea of what is required. Alternatively,
you can write procedures first, and later put them together to form a
program. This is known as bottom-up programming, and has the
advantage that the procedures can be written so as to be useable in
other programs as well. Note that the name bottom-up
programming has been mistakenly given to unstructured programming
by some proponents of top-down programming. Which you

					16

actually choose depends upon the nature of what you are doing.
Bottom-up programming is suited to trying out new ideas, when the
final form of the program is not too critical. Top-down programming
is suited to producing programs intended for long-term use.
	Another important aspect of programming is planning what you
are going to do in advance, before you start typing the program in.
This is often a problem, as programmers are usually eager to start
typing, and there is little point in simply writing out the whole
program first on paper. Two items of advice may be useful. One is
to plan on paper what structure the data in the program is going to
take. The coding will then take the form of writing procedures to
deal with this data. The other is to plan how the inputs and outputs
of the program will appear on the screen. The sheets of squared
paper included with this manual should help you design screens for
input/output.

			Arrays and subrange types
	You have seen the predefined data types CARDINAL, INTEGER,
CHAR, BOOLEAN, REAL etc. In this section you will see how to
define data types yourself. Such data types may be given a name in
a type declaration, which appears in the declaration section of the
program (or procedure)
	TYPE name=TypeDefinition;
	The following is the form of a type definition for a subrange
type:
	typename[const1..const2]
	The values of this type will be values of the type named (the
base type), but only those values in between the two constants will
be included in the subrange type. The base type must be an ordinal
type, thus the subrange type is also an ordinal type. The name of
the base type may be omitted, when the base type will be deduced
from the type of the constants. A subrange type is compatible with
its base type.
	The use of subrange types makes your program more readable,
as it is clear what range each of the variables may take. It also helps
you to find errors, as if an expression is outside the expected range,
an appropriate error message will be given.
	A program may need to deal with several items of data of the
same type, and furthermore it may be required to access each of
these items by an index number, rather than giving them each a
separate name. Array variables serve this purpose, and the
individual items of data are called elements of the array. The

					17

definition of an array type is as follows:
	ARRAY Indextype OF type
	The types here can be given as the names of types or can be
type definitions themselves. The index type must be an ordinal type,
and will typically be a subrange type. Note that it does not have to
be numeric.
	To access an element of an array, its index is put in square
brackets after the name of the array variable. This element may be
used anywhere in your program where a simple variable might be
used. For instance, an array variable may be declared as
follows:  VAR nums:ARRAY [1..10] OF CARDINAL  when the third
element of the array would be accessed as nums[3].
	Note that the index does not have to be a constant; it can be
any expression compatible with the given index type. A particularly
common case is in a FOR loop, when the index is the FOR control
variable, and so the elements of the array are processed one at a
time.
	One important use of arrays is something you have probably felt
has been missing so far; that is the storage of character strings.
This is done using variables of the type ARRAY [0..C] OF CHAR
where C is a constant; e.g. VAR s:ARRAY [0..7] OF CHAR;
	String constants can be assigned to such variables;
e.g. s:='Monday'. If the string constant does not take up the
whole of the array then it is terminated with a null character (CHR(0))
to mark the end.
	Note that the dimension of an array is fixed when the program
is compiled. However it is possible to write procedures which can
accept arrays of different dimensions as parameters by putting
ARRAY OF typename as the type of the formal parameter instead
of just the name of a type. This is called an open array parameter.
Thus a procedure to deal with character strings would have
parameters of type ARRAY OF CHAR. Such procedures are RDSTR
and WRSTR which deal with the input and output of strings. Further
procedures to deal with character strings are available in the library
module STRINGS.
	If A is an open array parameter then the first element of the
array passed to it will be mapped to A[0]. The index of the last
element is given by the function HIGH(A).
	Note that open array parameters may be either a value or a
variable parameter. The implementation of value open array
parameters is not strictly correct in this compiler, as they behave in
the same way as variable open array parameters, rather than

					18

duplicating the array which is passed.
	If the type of a formal parameter of a procedure is ARRAY OF
WORD, then the actual parameter may be of any type, and is
interpreted as an array of bytes in the procedure.

			Records and the WITH statement
	Sometimes an item of data may have several different parts. In
order for you to be able to encapsulate these into one item, it is
possible to define record types, as follows:
	RECORD
	fieldname,...:type;
	.
	.
	END;
	The individual parts are called fields of the record. Note that
they are declared within the record type in the same way as normal
variables are declared in a program. A given field of a record
variable is accessed by putting the name of the record variable
followed by a dot (i.e. a full stop) followed by the field name.
	A frequently used data structure is an array of records. For
example, the following declarations could be used to specify the
corners of a triangle.
	TYPE coordinate=RECORD x,y:INTEGER END;
		triangle=ARRAY [1..3] OF coordinate.
	VAR r:triangle.
	The coordinates of the first point of the triangle t would then be
r[1].x and r[1].y.
	Often when dealing with a record variable there will be several
statements each dealing with one field of the record. To avoid
having to give the record variable each time, the WITH statement is
available.
	WITH RecordVariable DO Statements END
	Any reference to a field name within the statements in the WITH
statement is taken as referring to the field of the record variable.
	By now you should be seeing how certain control structures go
with certain data types. This means that if you plan the data
structures of your program first, then writing procedures to
manipulate this data is usually straightforward. For example, the
following is a procedure to input an item of the above data type
triangle.
	PROCEDURE InputTriangle(VAR t:triangle);
	BEGIN

					19

	WRITELN('Please enter the coordinates');
	WRITELN('Press ENTER after each number');
	FOR i:=1 TO 3 DO
		WITH t[i] DO
			READ LN(x);READLN(y) END END
	END InputTriangle;
	You should now write a program with similar procedures, one to
print the coordinates of the triangle on the screen, and another to
plot out the triangle on the screen.


			Enumerated types and variant records
	So far all the ways of defining types have been in terms of
other types. The enumerated type is different, it is composed of a
list of the identifiers, representing the values of the type.
	(name0,name1,...namen)
	This is an ordinal type, the order being that of the list. You may
wonder what the point of enumerated types is, as you could use the
numbers 0 to n instead. One point is that they can make your
programs more readable, as the values can be given meaningful
names. Also errors are more likely to be detected, since
enumerated types are not compatible with other types. The function
ORD can be used to convert a value of an enumerated type to the
corresponding CARDINAL value. The reverse function is VAL, which
takes the form  VAL(OrdinalType,CardinalExpression) , giving a value
of the ordinal type indicated.
	It is a common mistake to assume that the names of the value
of an enumerated type can be used as strings, for instance by using
WRITELN with such a value and expecting its name to be printed.
This is not the case; to achieve such an effect a routine to deal
specifically with the enumerated type must be written. Another
mistake is to try to put values of some other type, such as
characters, into an enumerated type; e.g. ('A','E','I','O','U').
	Sometimes when storing information in a record variable, some
fields may only be used when a particular field has a certain value.
For instance, if you were storing information about vehicles in an
array of records, those records relating to cars would need to store
different types of information to those relating to bicycles. To give
for this sort of flexibility, record types are allowed to have variants;
that is, several field lists may be given and the one which applies in
a particular case determined by the value of a field of the record,
called the tag field. This structure goes in the record type definition,


					20

and has the following form:
	CASE tagname:type OF
	const1:fieldlist|
	const2:fieldlist|
	.
	.
	ELSE fieldlist
	END
	To access components of a record variable from a field list
within a variant, the tag field must have the value of the constant at
the start of the variant. Again a range or list of constants may be in
this position. It is permissible to omit the tag name, meaning that
any of the components may be accessed. However, the variants all
occupy the same storage space, so this must be used with care.


				Set Types
	Supposing you wanted to test whether a variable ch of type
CHAR held one of a given set of characters, say the vowels. It
would be possible to put these characters into an array and test ch
against each of them in turn. However there is a more direct way,
since Modula-2 allows SET types, defined as follows:
	SET OF OrdinalType
	The ordinal type is known as the base type of the set. Once a
set type has been defined, a set of that type can be constructed by
putting the name of the type followed by the items of the set in
braces; i.e. { and }. The type of these items must be the base type
of the set. A range of items may also be specified.
	typename{expression,expression..expression}
	To test whether a given item is in a set, the operator IN is used,
giving a BOOLEAN result:
	expression IN SetExpression
	The expression must be compatible with the base type of the set.
Thus, for the example given, letterset=SET OF ['A'..'Z'], would define
the type and  CAP(ch) IN letterset{'A','E','I','O','U'}  would be the
required test. (If ch is a lower case letter, then the value of CAP(ch)
is the capital letter, otherwise it is just ch.)
	If v is a variable of a set type, and x is a value of the base type,
then the procedure INCL(v,x) will include the value x into the set v,
while EXCL(v,x) will remove x from v.
	The operators +, *, - and / apply to sets, denoting set union,
set intersection, set difference and symmetric set difference

					21

respectively. The comparison operators =, <>, <=, >= also apply to
sets, testing for set equality and whether one set is a subset of
another.
	There is no restriction, with this compiler, on the number of values
of the base type of a set. However, there are restrictions on how set
types whose base types have more than 16 values can be used.
Firstly, a set constructor of such a type may only have constant
expressions. Secondly, the operators +, -, *, / do not apply to such
types; instead there are corresponding procedures in the module
SETOPS.
	There is a predefined set type BITSET, defined as SET OF [0..7].
Set constructors type BITSET do not require the type name before
the braces. Since sets are stored in memory as bitmaps, with each
bit indicating whether a given value is present in the set, the type
BITSET can be used in low-level programming to access the
individual bits of a byte in memory.

			Pointer types and recursion
	So far, the storage requirements for all of the data dealt with by
a program have had to be specified when the program is written.
Sometimes it is useful to be able to allocate space for data
dynamically, that is at run time rather than compile time. Such data
is accessed in the program by what is called a pointer variable.
Pointer types are defined as follows:
	POINTER TO typename
	Note that the type of the data item pointed to has to be
specified. For example you could declare a variable  VAR p:POINTER
TO REAL. To create the space to store a real value and make p
point you should use ALLOCATE(p,TSIZE(REAL)). This creates what
is known as a dynamic variable, which is pointed to by p, and is
refered to as p^. (The symbol ^ is obtained using SYMBOL SHIFT &
H on the Spectrum). The procedure ALLOCATE is from the module
STORAGE. (This compiler does not allow the use of NEW, as some
compilers do.) Space is obtained from an area of memory called the
heap. The heap is positioned between RAMTOP and the start of the
Modula-2 program, which is given by the number following CLEAR
after compilation. Thus you should decide how much memory is
required for the heap, and CLEAR that amount below the number
given. Methods of returning memory to the heap are given in the
description of the module STORAGE in section 5.
	Using ARRAY and RECORD types you can build up many
different data structures. However these all essentially have form of a

					22

table of data of fixed size. Using pointer types allows you more
flexibility. For instance the following would represent a tree structure
of character strings.
	TYPE nodeptr=POINTER TO node;
		node=RECORD
			data:ARRAY [0..9] OF CHAR;
			leftbranch, rightbranch:nodeptr
		END;
	Note that normally an identifier must be declared before it is
used in another declaration, but pointer type definitions are an
exception to this rule. This allows definitions such as the one above,
known as recursive definitions. Modula-2 also allows for a
procedure to be recursive; that is to contain a call to itself. This can
be useful when processing data structures such as the one above
and for other purposes; e.g. the QUICKSORT routine in the module
DATA.
	To fully understand the use of pointers in building data
structures requires plenty of examples. If you are intersested, you
will find that any book with "data structures" in its title is mainly
concerned with this aspect of programming.
	There is a predefined pointer type called ADDRESS from the
SYSTEM module, defined as POINTER TO WORD, which can be
thought of as holding a memory address. This is compatible with
any pointer type, and with CARDINAL, and may be mixed with
values of type CARDINAL in expressions. Also, if a VAR parameter
is of type ADDRESS, then a variable of any pointer type may be
passed to it. The type ADDRESS allows direct access to memory
addresses and so should be used with care. The function ADR
applied to a variable gives a result of type ADDRESS which is the
address of the variable in memory.

			Modules and procedure types
	If you look at the library modules supplied you will see that they
have the form:
DEFINITION MODULE name;
definitions
END name.
IMPLEMENTATION MODULE name;
body of implementation module
END name.
	The definition part may contain declarations of constants,
variables etc., just as in a program module. Only the headings of

					23

procedures are put here, with the full procedure in the implementation
part. The definition part indicates what can be used by
programs importing the module. The form of the part following the
word IMPLEMENTATION is the same as a normal program module.
As well as containing the full form of the procedures declared in the
definition part it may contain an initialization section. This occupies
the position of the main program in a program module. The
initialization sections of all of the modules imported by a program
are executed before the execution of the program itself starts.
	The library modules supplied are essentially just collections of
related procedures, but there is another reason for using modules.
This is to group together the definition of a data type with the
procedures acting on items of that type. For instance the module
RADECSTG deals with the RA, declination, spectral type and Greek
letter of a star, defining types to represent these and procedures
which input and output variables of these types.
	In this compiler the definition and implementation parts of a
module are in the same file. With other compilers they will usually
be separate files, indeed the implementation part may be in
compiled form, and so not readable by the user. This hiding of
information about the implementation of imported procedures is an
important part of good programming practice, as it enforces the
independence of the separate parts of the program. As well as
hiding the implementation of procedures, it is also possible to hide
the internal structure of a data type, by using what is called an
opaque type. This has simply the name in the definition part of the
module; i.e.  TYPE typename1;  and the full declaration in the
implementation part  TYPE typename1=POINTER TO typename2;.
Note that opaque types must be pointer types.
	As well as the above sort of module which is in a separate file to
your program, it is possible to define local modules. These do not
have definition and implementation parts, instead the items from the
module which are for use outside the module are put in an export
list.  EXPORT item,...; . The form of a local module is as follows:
	MODULE name;
	imports;
	export list
	declarations
	BEGIN
	statements
	END name;

					24

	The module has an import list, as items from outside the
module are not recognised inside the module unless they are
imported. The statements following BEGIN form an initialization part
for the module.
	You will have noticed that the procedures in the library modules
are mostly implemented in machine code. This is done by putting
HEX instead of BEGIN in a procedure. Following HEX a sequence
of values in hexadecimal form are required, which are interpreted as
bytes of machine code (note that these are not followed by 'H').
CARDINAL constants are allowed if placed in brackets, when they
are interpreted as two bytes of code. The operator @ may be used
here. This gives the address of global variables or of string
constants, and the start address of procedures, each time as a
constant of type CARDINAL. The procedure ends with the word
END and the procedure name as usual. It is not recommended that
you try to write this sort of procedure, as they require a knowledge
of how the compiler passes parameters. It is hoped that a MIRA
SOFTWARE Assembler package which includes a facility to simplify
the writing of such procedures will be available soon.
	You have probably also noticed that some of the procedures
available from the library modules are not declared as procedures in
the normal way. Instead they are declared as variables of a
procedure type. The definition of a procedure type is similar to a
procedure heading, but it does not have the procedure name, or the
names of parameters, just the parameter types. A variable of a
procedure type may have a procedure with the appropriate heading
assigned to it. The procedure variable may then be called, in the
same way as a normal procedure.
	An example is the procedure WRSTR in the procedure STDIO.
This is declared as a variable of type PROCEDURE(ARRAY OF
CHAR), and has the actual procedure SWRSTR assigned to it in the
initialisation section of STDIO. However, the alternative output of the
module FASTO has a different procedure FWRSTR for the same
purpose, and assigns this procedure to WRSTR instead. The point
of this is that only one version is required of a procedure such as
WRCARD from the module NUMIO, as this will call WRSTR and so
use whatever method of output has been selected. Another use for
procedure types would be in a graph drawing program, which
obtained the function whose graph is to be drawn as a parameter of
a procedure type. The type of a procedure without parameters
would thus just be defined as PROCEDURE, but is given the
predefined identifier PROC.

					25

Concurrent Programming
	So far if a program has had to do more than one task, they have
been executed sequentially; i.e. completing one before starting
another. However there are situations where tasks need to be
executed concurrently; i.e. starting a task while others are still in
progress. These tasks can be thought of as being independent
subprograms, and are called processes. Thus so far programs have
consisted of a single process, whilst in concurrent programming the
program starts as a single process which will initiate other
processes. Note that since the Spectrum has a single processor,
only one process can be active at a time. Transfer of activity
between processes may be caused by commands in the processes
themselves, or by some external cause, which for this compiler
means the Spectrum's 50Hz clock.
	Processes are implemented as procedures without parameters.
Note that whereas normally a procedure would perform a certain
task and then return to the calling program, a process will typically
be an endless loop, with an explicit transfer statement within it. As
several processes can be active at the same time, they cannot use
the same stack for storage of variables etc., so each needs a stack
within its own workspace. This workspace must be large enough to
store the process's variables plus those of any procedures it calls,
as well as any temporary storage required. When a process is
inactivated, the stack pointer is assigned to a variable of type
ADDRESS. This variable can be thought of as holding the
suspended state of the process.
	To create a process the procedure NEWPROCESS is called:
	NEWPROCESS(P,wsp,len,sto)
	P is the procedure which forms the required process. wsp and len
are the start and length of the workspace for the process. The
process is created in an inactive state, which is stored in the variable
sto.
	To transfer between processes the procedure TRANSFER is
called:
	TRANSFER(src,dst)
	This suspends the current process, storing its state in src and
resumes the process whose inactive state has been stored in dst.
	In the case where transfers are to be effected by the
Spectrum's 50 Hz the procedure IOTRANSFER is used:
	IOTRANSFER(src,dst)
	When called this has the same effect as TRANSFER. However, it
also sets an interrupt routine so that when an interrupt occurs the

					26

reverse transfer occurs, so continuing with the statement following
IOTRANSFER. Typically IOTRANSFER occurs in a loop, which is
executed once each time an interrupt occurs.
	The procedures NEWPROCESS, TRANSFER and IOTRANSFER
are all from the SYSTEM module.
	When using interrupts it is sometimes necessary to ensure that
processes are not interrupted at critical points. In the definition of
Modula-2 this is done by specifying a priority in the heading of a
module, meaning that it can only be interrupted by interrupts of
higher priority. However, this is not implemented by this compiler.
Instead, some of the procedures from the module GENERAL need
to be used. The procedure DI will disable interrupts, and they can
be enabled again using El. However, using these means that
interrupts may be ignored. Alternatively the procedures LOCK and
UNLOCK can be used. LOCK changes the interrupt routine for a
routine which simply counts the interrupts. UNLOCK changes the
interrupt routine back to the original one, and runs it the number
counted since LOCK, thus making up for any lost interrupts.
	The procedure TESTRES may also be useful when using
interrupts. This gets the value of a Boolean variable and sets the
variable to FALSE in a single processor operation.
	The procedure IM1 resets the interrupt mode to 1, effectively
"turning off" the interrupts set up using IOTRANSFER.

					27

		3: A summary of the implementation
	This section is concerned with some details of how the
MODULA-2 language is implemented by this compiler.

				The ordinal types
	The value of MAX(INTEGER) is 32767, and MIN(INTEGER) is
-32768. MAX(CARDINAL) is 65535. INTEGER and CARDINAL
require two bytes of storage. The type CHAR consists of all of the
256 characters in the Spectrum character set, and all are allowed as
string characters. CHAR requires a single byte. The storage
required by enumerated and subrange types depends on the range
of the ordinal numbers of the values of the type. If this is within
0..255 then it will need one byte, otherwise two bytes.

				The real types
	REAL (or REAL5) variables are stored in five bytes with a four
byte mantissa and one byte exponent; i.e. the same as in Basic.
Thus they are stored internally to an accuracy of about 10 places of
decimals, of which up to 8 may be printed out. REAL2 and REAL3
variables have a one byte exponent and a mantissa of one and two
bytes respectively.

					Sets
	A set is stored by the compiler by having one bit for each value
of the base type of the set. The bit is set if the value is an element of
the set. Thus the number of bytes used is approximately (number of
values of the base type)/8.

				Comparisons with Pascal
	Modula-2 is a descendant of Pascal, and if you know how to
program in Pascal, then you will easily pick up Modula-2. The main
differences are listed below.
1) Modula-2 programs begin with the word MODULE rather than the
word PROGRAM.
2) Most procedures used need to be imported from other modules,
rather than being built in to the compiler.
3) The name of each procedure and module has to be repeated at
the end of its declaration after the word END.
4) Most of the control statements finish with the word END. This
means that BEGIN is only used to mark the start of the statements,
not to form compound statements.
5) In Modula-2 capital and lower case letters are considered distinct.

					28

		Non-standard features of the implementation
	The following are the areas in which this implementation differs
from that described in the 3rd edition of Wirth's book "Programming
in Modula-2":

	1) The direct specification of the position of a variable, by
putting the address in square brackets, comes directly after the
keyword VAR and applies to all variables declared in that section,
the variables being positioned one after another.
	2) Value open array parameters are not copied, but instead
behave in the same way as variable open array parameters.
	3) Module headings may not contain priorities.
	4) If a procedure heading or type occurs in a definition module,
and the full declaration is in a local module in the corresponding
implementation module, then the name must be in the import list of
the local module, rather than the export list.

				MODULA-2 Reserved Words
IMPORT    EXPORT    FROM      QUALIFIED
IMPLEMENTATION      DEFINITION
CONST     TYPE      VAR       MODULE    PROCEDURE
ARRAY     RECORD    SET       POINTER
BEGIN     END       RETURN
IF        THEN      ELSE      ELSIF     CASE
WHILE     REPEAT    UNTIL     LOOP      EXIT
FOR       TO        BY        WITH      DO        OF
AND       OR        NOT       MOD       DIV       IN


				Predefined Identifiers
REAL      REAL5     REAL3     REAL2     CARDINAL  SHORTCARD
CHAR      BITSET    PROC      INTEGER   BOOLEAN
TRUE      FALSE     NIL
HIGH      MAX       MIN       SIZE
CARD      INT       RL5       RL3       RL2
VAL       ORD       CHR       FLOAT     TRUNC
WRITE     WRITELN   READLN    HALT
INC       DEC       INCL      EXCL
ABS       ODD       CAP
	The procedures WRITE, WRITELN and READLN are included in
this implementation for simple input and output. READLN will input
variables of the numeric types or of type CHAR. WRITE and
WRITELN will output expressions of the above types, as well as

					29

constant character strings. These procedures always use the
standard Spectrum I/O channels, even when some other method of
I/O has been selected.

		Compiler directives and how the compiler works
	Normally comments are ignored by the compiler. However, if
the character immediately following the (* token is a dollar symbol
$ then the comment is taken to be a directive; that is it controls
some aspect of compilation, depending on the character following
the $. The first of these is the letter R; i.e. (*$R *). Using this
directive specifies that operators on REAL2 expressions are not
required, thus freeing some memory.
	As was mentioned in the section on the options byte in part 2,
adding 64 to (i.e. setting bit 6 of ) the options byte leads to the
compiled program and its symbol table to be saved after compilation,
with filenames #pname and $pname respectively, where 
pname represents the name of the program. If this has been done
then the following directive can be used in other programs:
	(*$B pname*)
	The module specified in the directive (i.e. pname in this example) is
called a base module. This directive results in the compiled code of
the base module being automatically loaded into memory on
compilation of the program, and it is run at the start of the program.
Any modules imported by the base module will thus not need
recompiling.
	In fact, when you are developing a program, it is best to write a
base module which imports all of the completed modules which you
intend to use. This way only the modules which you are actually
developing need to be recompiled each time.
	The amount of memory available for programs on the Spectrum
is fairly limited. One way of fitting in a program that is too large to fit
into memory is to use overlays. To do this you should use the
directive (*$O mname*). The compiled module given by mname is
used as a base module. All of the modules imported into the
program which do not come from the base module are compiled so
as to use the same area of memory, rather than being one after the
other as they would be without the directive. They are saved to the
drive as they are compiled with filenames of the form %name. The
program has to be written to load these modules in when they are
required. Typically the module DRIVES would be part of the base
module and the calls to the loading routines would be in the main
program module. Using overlays in a program needs considerable

					30

care; for example one overlay module cannot call a routine from
another of the overlay modules. Overlays are best suited to
programs which have a number of options in the main module, when
each option can dealt with by a different module, or else to
programs which perform a number of different tasks in sequence.
	The directive (*$E- *) can be used to turn off some of the
run-time error checks. This will mean that the program will run
faster. If you intend to use this, you are advised to put it in after the
program has been debugged, rather than put it in as soon as you
start writing a program. The suppression of errors may also be
useful for other purposes, such as allowing addition of CARDINAL
values to be done modulo 65536. Error checking may be turned on
again by the directive (*$E+ *). The directive (*$E/ *) restores the
error checking status to that before the last (*$E+ *) or (*$E- *).
	An R, B or O directive may only be placed at the start of the
program module and only one may be used. The E directives may
be placed anywhere in the source code.
	The compilation of a program, and of each of the imported
modules, takes place in several passes. The first pass scans the
list of modules imported by the module. Each of these must be
compiled before the module itself is compiled. The second pass
scans the definition part of the module, to create the corresponding
part of the symbol table. The third pass scans the declarations in
the module. The fourth pass is a preliminary scan of the statements,
and the fifth pass determines the position of the code and variables.
On the sixth pass the code is produced, completing the compilation
of the module. The code is postioned after the Spectrum's system
variables, and before the source code while compilation is in
progress, then it is moved to the correct position when all the
modules have been compiled.
	The positioning of each module goes backwards from the start
of the run-time part of the compiler; thus the main program code,
which is the last to be compiled, ends up before the code for the
modules which it imports. The compiled part of each module
consists of three sections; for global variables, constants and the
actual machine code. After compilation, the number after CLEAR is
the start of the main program's global variables, and that after SAVE
is the start of the main program's constants.
	Note that if a module imports other modules then it may have to
be loaded twice, first to scan the modules which it imports, then to
compile it after these modules have been compiled. This can be
avoided by working out the hierarchy of modules, then importing all

					31

modules into the main program, with those at the bottom of the
heirarchy last in the import list. The compiler works backwards
through the import list, so those at the end will be compiled first.

				The symbol table
	While compilation is in progress the symbol table contains the
declarations from the definition part of each of the modules which have
been compiled, but it only contains the declarations from the
implementation part of one module at a time. One problem which
you are likely to run into as your programs get bigger and import
more modules is running out of symbol table space. This can be
seen as the symbol table overflows the screen attributes area and
overwrites other memory, causing a crash. If this happens then you
should check through the main module to ensure that there are no
items declared which are not used. Another way of reducing symbol
table size is to alter any items in the import list which have the
form  FROM module IMPORT item,...; to IMPORT module;  and
using the form module.item for the imported items. If these methods
do not succeed, then a further possibility is to examine the definition
parts of the modules which you are importing, and comment out
those parts which you do not import into your program. Sometimes
a procedure will be called without you realising it, leading to the
message "UNDECLARED IDENTIFIER", and in particular it is
recommended that none of the module STDIO is commented out. A
similar method can be used to reduce the run-time size of the
program by commenting out unused code in the implementation
parts, but this tends to be more risky and is usually not worth it.

				4: ERROR MESSAGES.

				Compile-time errors
	The following is a list of possible compile time error messages
with their numbers, and some explanation where appropriate.

4, Out of memory : The MODULA-2 program is too long.
11, Integer out of range
44, END OF SOURCE FILE : The program is not complete.
44, RESERVED WORD
45, IDENTIFIER DECLARED TWICE
46, TYPE EXPECTED
47, TYPE MISMATCH
48, CONSTANT EXPECTED
49, VARIABLE EXPECTED

					32

50, ORDINAL EXPECTED
51, FUNCTION ERROR
52, EXPRESSION OUT OF RANGE
53, RECORD EXPECTED
54, INVALID FOR VARIABLE
55, ARRAY EXPECTED
56, POINTER EXPECTED
57, NAME EXPECTED
58, SYNTAX ERROR
59, CIRCULAR MODULE REFERENCE
60, INVALID OPERATOR
61, UNDECLARED IDENTIFIER
62, SET EXPECTED
63, PROCEDURE MISMATCH
64, TERMINATOR EXPECTED
65, WRONG NUMBER OF PARAMETERS
66, LENGTH ERROR
67, UNDEFINED VARIABLE
68, INVALID NUMBER
69, MODULE NAME EXPECTED
70, NUMBER EXPECTED
71, INVALID RETURN IN MODULE
72, DRIVE ERROR
73, LOADING ERROR: Probably a bad sector.
74, SAVING ERROR : Probably the drive is full, or else the directory
is full (this often causes confusing drive errors).
75, FILE NOT FOUND
76, WRONG FILE TYPE
77, ERROR FOUND
78, ERROR NOT FOUND
17, This will be followed by a message stating that a particular
character, token or word is expected.

	Another possible error is the symbol table getting too big, which
would cause the compiler to crash. As this table is stored in screen
memory you can see how much space it takes. See the previous
section on the symbol table to deal with this.

				Run-time errors
	The following are the possible run time errors:

1, WRONG TAG VALUE: Occurs with record variants.
2, NIL POINTER: A pointer is NIL or undefined.
3, RANGE ERROR: A variable of a subrange type has been assigned
a value outside its range.
4, DRIVE ERROR: There has been an error in writing to or reading

					33

from the drive.
The errors HEAP OVERFLOW and DEALLOCATE ERROR may also
occur if the module Storage is used.
	Following one of the above run-time errors, the message
CONTINUE (Y/N) will appear. Pressing 'Y' means the error will be
ignored, and the program will continue running, otherwise it will stop
with the Basic error message "Break into program". Following this it
is possible to find the position of the error in the source code by
reentering the compiler, reloading the program and setting bit 5 of
(ie. adding 32 to) the options byte. On pressing the 'X' key, the
compiler will not produce a compiled program, but will search for the
position of the error instead. If it is found then an "ERROR FOUND"
message will be given, otherwise the message "ERROR NOT
FOUND". In the latter case the error may have been in a base
module, and so you should try again with the source code for this.
	Note that error messages are sometimes confusing. In the
statement:  WRITELN('Value is' x)  a comma is missed out but
the compiler will report ')' EXPECTED. In the statements:
	x:=y+z
	writeln(x)
the missing semicolon will be reported at the beginning of the
second line, not at the end of the first.

			5: The library of modules
	The following pages describe what is available from the library
of modules included with the compiler. For each module the
definition part is given here, followed by a description of the purpose
of each item.
	The module SYSTEM is built in to the compiler, rather than
having to be loaded separately. The items from it do not follow the
normal rules, but it can be thought of as having the following form:
DEFINITION MODULE SYSTEM
TYPE WORD;
	ADDRESS = POINTER TO WORD;
PROCEDURE ADR(variable);
PROCEDURE TSIZE(typename);
PROCEDURE NEWPROCESS(P:PROC; A:ADDRESS; n:CARDINAL;
			VAR q:ADDRESS);
PROCEDURE TRANSFER(VAR src,dst:ADDRESS);
PROCEDURE IOTRANSFER(VAR src,dst:ADDRESS);
END SYSTEM.

					34

	The modules Terminal, InOut, RealInOut, Mathlib0, LineDrawing
and Processes are included with this compiler but are not described
here as it is assumed that if you use them then you will be working
with a book containing their description.

DEFINITION MODULE STDIO;
VAR RDSTR:PROCEDURE(VAR ARRAY OF CHAR);
	RDCHAR:PROCEDURE():CHAR;
	WRSTR:PROCEDURE(ARRAY OF CHAR);
	WRLN,CLS:PROC;
	GOTOXY:PROCEDURE(CARDINAL,CARDINAL);
	MOVEX:PROCEDURE(INTEGER);
	WRCHAR,CURSOR,UNCURS,
	MODECHNG: PROCEDURE(CHAR);
	GETXY:PROCEDURE(VAR CARDINAL,VAR CARDINAL);
PROCEDURE KREADY():BOOLEAN;
PROCEDURE GETKEY():CHAR;
PROCEDURE WAITKEY():CHAR;

PROCEDURE PRINTABLE(C:CHAR):BOOLEAN;
PROCEDURE RDCHAR1():CHAR;
PROCEDURE WRSTRLEN(S:ARRAY OF CHAR; L:CARDINAL);
PROCEDURE WRSPS(NUM:CARDINAL);
PROCEDURE WRSUBSTR(S:ARRAY OF CHAR;
	Start, End: CARDINAL);
PROCEDURE RDITEM(VAR S:ARRAY OF CHAR);
PROCEDURE GENRDSTR(VAR S:ARRAY OF CHAR);
PROCEDURE BUFFRDCHAR():CHAR;
PROCEDURE STDOUT(STRM:SHORTCARD);
PROCEDURE STDIN(STRM:SHORTCARD);
PROCEDURE KBDIN;
(*
PROCEDURE SWRSTR(S:ARRAY OF CHAR);
PROCEDURE SUNCURS(C:CHAR);
PROCEDURE SWRCHAR(C:CHAR);
PROCEDURE SWRLN;
PROCEDURE SCLS;
PROCEDURE SGOTOXY(X,Y:CARDINAL);
PROCEDURE SCURSOR(C:CHAR);
PROCEDURE SMOVEX(N:INTEGER);
PROCEDURE SGETXY(VARX,Y:CARDINAL);
PROCEDURE SRDSTR(VAR S:ARRAY OF CHAR);
PROCEDURE SMODECHNG(C:CHAR);
*)
END STDIO.
	The first procedures in this module are those required for input
and output. These are procedure variables, and are set by this

					35

module to be procedures based on the Spectrum's normal input and
output channels, but can be altered to give a different method of
input/output as is done by the modules FASTO and MAINREAD.
	To start with, the input channel is 1 (keyboard), and the output
channel 2 (screen), but these can be altered using STDOUT and
STDIN; for example STDOUT(3) would send subsequent output to
the printer. If the input channel has been changed, then to reset it
to the keyboard the procedure KBDIN should be used (rather than
STDIN(1)). These procedures are also used to reset input/output
procedures back to using the Spectrum's channels.
	The procedure RDSTR reads a line of input into a string variable,
whilst RDCHAR can be used to read the input one character at a
time. WRCHAR sends a character to the output, and WRSTR sends
a string. WRLN moves to the next line of output. CLS is used to
clear the screen. GOTOXY moves the printing position to a given
position on the screen, while GETXY can be used to read the current
printing position. MOVEX moves the printing position horizontally by
a given number (positive or negative) of character spaces. The
procedures CURSOR, UNCURS, and MODECHNG are used by the
module MAINREAD.
	The functions KREADY, GETKEY and WAITKEY enable the
program to respond to single keypresses. KREADY indicates
whether a key has been pressed. If a key has been pressed,
GETKEY returns the character, otherwise it returns 0C. This cannot
deal with mode changes, it simply returns the key code. WAITKEY
waits for a key to be pressed, and deals with mode changes.
Following a mode change WAITKEY calls MODECHNG. This is set
up to print the mode character ('L', 'C', 'E', or 'G') in the top right of
the screen, but a different MODECHNG procedure could be written.
	The procedure WRSTRLEN writes a string using  len  character
positions, putting spaces before it if necessary. If the string is longer
than  len  then the whole string will still be written. WRSPS writes a
given number of spaces. WRSUBSTR writes the substring of S
starting at position  start  to the position  end. If the end of the string
comes before the position  end, the appropriate number of spaces
will be printed after it. Thus WRSTRLEN can be used to right justify
a string and WRSUBSTR to left justify a string. While the procedure
RDSTR reads everything in a line of input up to the end of line
character, the procedure RDITEM ignores any initial spaces, then
reads up to the next space, so it can be used to read several items
on the same line.
	Reading a line of input from the keyboard involves buffered

					36

input; i.e. the input is stored in a buffer, which is not available to the
program until ENTER is pressed. The advantage of this is that it
allows the input to be altered while it is being typed. For other forms
of input; e.g. disc files, this is not needed, so it is better to be able to
read characters one at a time. This shows why the procedure
KBDIN is needed; it gives buffered input while STDIN selects
unbuffered input.
	If you want to write different input procedures, then it is
necessary to decide which of these apply. If unbuffered input is
required then a procedure should be assigned to RDCHAR and then
RDSTR:=GENRDSTR;  will supply the required RDSTR procedure.
Alternatively, for buffered input a procedure for RDSTR should be
written, and then  RDCHAR:=BUFFRDCHAR;  will supply the
required RDCHAR procedure.

DEFINITION MODULE DRIVES;
PROCEDURE OPEN(Drive: SHORTCARD;
	FileName:ARRAY OF CHAR;Channel:SHORTCARD);
PROCEDURE CLOSE(Channel:SHORTCARD);
PROCEDURE EOF(Channel:SHORTCARD):BOOLEAN;
PROCEDURE ERASE(Drive:SHORTCARD;
	FileName:ARRAY OF CHAR);
PROCEDURE RELOAD(Drive:SHORTCARD;
	FileName:ARRAY OF CHAR);
PROCEDURE SAVE(Drive: SHORTCARD;
	FileName:ARRAY OF CHAR;START,LEN:CARDINAL);
PROCEDURE LOAD(Drive: SHORTCARD;
	FileName:ARRAY OF CHAR;START:CARDINAL);
PROCEDURE CAT(Drive:SHORTCARD);
PROCEDURE RDEXISTS(FileName:ARRAY OF CHAR):BOOLEAN;
PROCEDURE RDROOM(): CARDINAL;
CONST D=0BD9BH;
VAR ExistAction:(Error,Overwrite);
VAR [D+31] Bch:CHAR;
VAR [D+35] FileLen:CARDINAL;
END DRIVES.
	This module is concerned with the the use of disc and other
drives, the ramdisc, and tape. The operation of most of the
procedures should be clear as they correspond to Basic commands.
The drive number should be 0 if tape is required and 13 for
ramdisc. A drive number of 15 will save to both drive and ramdisc,
and will try ramdisc first and then the drive for loading.

					37

The saving and loading procedures deal with blocks of bytes; i.e.
CODE type files. There are two loading procedures: LOAD which
needs the position of the code to be given; and RELOAD which
loads it at the position it was saved from. The variable ExistAction
specifies what action is to be taken if saving a file which already
exists on the drive. This is set to Overwrite to start with, which
means that the old file is replaced by the new one. Setting it to
Error means that an error would be reported instead.
	CAT gives a catalogue of the ramdisc or drive. For most drives
this will be on the current output channel. ERASE deletes a file from
ramdisc or drive.
	OPEN connects a file on the given drive to one of the Spectrum
channels and CLOSE disconnects it, so the channel number should
be in the range 0 to 16. The channel can then be selected as the
current input or output channel using STDIN and STDOUT from the
module STDIO. The function EOF is used to test if the end of file
has been reached on a given channel which has been connected to
a file.
	The variable Bch is used by the Beta disc driver. Before calling
OPEN this needs to be set to 'R' for input and 'W' for output. Before
ERASE it should be set to '#' for serial files, and CHR(175) for code
files.
	The variable FileLen will contain the length of a code file which
has been loaded using LOAD or RELOAD, provided the drive is one
of : +3, Microdrive, Disciple, Opus.
	RDEXISTS is used to test whether a given file exists on the
ramdisc. RDROOM returns the amount of space available on the ramdisc
in kilobytes.
	Note that if a 48k version is being used then using drive 13 will
give an error or behave unpredictably, drive 15 will refer to drive 1,
the procedure RDEXISTS will always return FALSE, and RDROOM
will give 0.
	If you want to write a program which will work on any disc
system then it is best to transfer data to and from disc by saving and
loading blocks of bytes into program variables, rather than using
input and output routines with OPEN and CLOSE on disc files. You
will need to include the run-time drivers "RTAPE" to "ROPUS+2" with
your program. After loading the program, the run-time driver
appropriate to the system being used should be loaded.

					38

DEFINITION MODULE FASTO;
FROM SYSTEM IMPORT ADDRESS;
VAR XUDGS:ADDRESS;
PROCEDURE FASTOUT;
PROCEDURE WRITEAT(X,Y:CARDINAL;S:ARRAY OF CHAR);
(*
PROCEDURE FUNCURS(C:CHAR);
PROCEDURE FCURSOR(C:CHAR);
PROCEDURE FWRCHAR(C:CHAR);
PROCEDURE FWRLN;
PROCEDURE FWRSTR(S:ARRAY OF CHAR);
PROCEDURE FGOTGXY(X,Y:CARDINAL);
PROCEDURE FGETXY(VAR X,Y:CARDINAL);
PROCEDURE FMOVEX(X:INTEGER);
PROCEDURE FCLS;
*)
END FASTO.
	This module supplies faster routines for writing text to the
screen. To do this it just needs to be imported into the program.
FASTOUT will reselect these routines after a different output method
has been used. The speed is achieved by leaving out some facilities
of normal Spectrum screen output, such as control codes for
colours, etc. Also, printing will not scroll at the bottom of the
screen, so it is suited to printing separate screens of text, rather than
continuous printing. The routines will print characters between
CHR(32) and CHR(164) in the same way as normal. CHR(165) to
CHR(170) give various diagonal lines and blocks. Characters after
CHR(171) can be used to print further user-defined characters
stored at the address in XUDGS.
	The procedure WRITEAT writes a string directly to a given
position of the screen. It is independent of the other routines in that
it does not change the printing position, and can be used when
another method of output has been selected. This means it may be
useful within an interrupt routine.

DEFINITION MODULE MAINREAD;
VAR entrych,exitch:CHAR;
textmode:(overwrite,insert,overflow);
PROCEDURE EDITPREP(len:CARDINAL);
PROCEDURE EDIT(VAR S:ARRAY OF CHAR);
PROCEDURE EDITL(VAR S:ARRAY OF CHAR);
PROCEDURE BLANK(X,Y,L:CARDINAL);
PROCEDURE SETMAINRD;
PROCEDURE MRDSTR(VAR S:ARRAY OF CHAR);
VAR cur:CARDINAL;
END MAINREAD.

					39

	This module allows keyboard input to take place using the
upper screen. Simply importing it into a program will cause input to
use the required procedures. The procedure SETMAINRD will
reselect this method of input after another method has been used.
	It is also possible to use the procedures supplied to gain more
control over the entering of text on the screen. EDITPREP(len)
initialises the editing of a string at the current print position on the
screen, with  len  specifying the maximum length of the string to
be edited. Then EDIT(S) allows the string S to be edited. If you
want to edit a non-empty string, then it should be printed at the
appropriate position before EDIT is called. A return is made from
EDIT when a non-printing character is typed, or when the string
overflows the allowed length. The character causing the return is
placed in the variable  exitch. EDIT(S) can be called again to
resume the editing of the string if required. The variable  cur  is the
position of the cursor in the string. This is set to zero by EDITPREP,
but may be altered to give an initial position which is not at the start
of the string. If the variable  textmode  is insert, then during
editing, each character typed will be inserted into the string. If it is
overwrite then the character will overwrite the character at the cursor
position. The mode overflow is similar to insert, but if the string
overflows the allowed length then the characters at the end are lost.
	If the variable  entrych  has a non-null character on entry to
EDIT then this is used as if it were the first character typed.
	The procedure EDITL does the same as EDIT, but a return only
occurs when one of the enter, up, or down keys is pressed. If the
edit key is pressed then the string is cleared.
	The procedure BLANK(X,Y,L) prints L spaces at the position
(X,Y) then returns the print position to (X,Y).
	The following constants are declared within the implementation
part of this module. You may find it useful to copy these to you own
programs.

CONST	edit =07|;
	left =08|;
	right =09|;
	down =0A|;
	up =OB|;
	delete=0C|;
	enter =0D|;

					40

DEFINITION MODULE STRINGS;
FROM SYSTEM IMPORT ADDRESS;
PROCEDURE LENGTH(S:ARRAY OF CHAR):CARDINAL;
PROCEDURE CONCAT(S1,S2:ARRAY OF CHAR;
	VAR D:ARRAY OF CHAR);
PROCEDURE SLICE(S:ARRAY OF CHAR;P,L:CARDINAL;
	VAR R:ARRAY OF CHAR);
PROCEDURE COPY(S:ARRAY OF CHAR;
	VAR D:ARRAY OF CHAR);
PROCEDURE APPEND(VAR D:ARRAY OF CHAR;
	S:ARRAY OF CHAR);
PROCEDURE COMPARE(S1,S2:ARRAY OF CHAR):INTEGER;
PROCEDURE INDEX1(S:ARRAY OF CHAR;C:CHAR):CARDINAL;
PROCEDURE INDEX(S,SS:ARRAY OF CHAR):CARDINAL;
PROCEDURE SEARCH(VAR P:ADDRESS;VAR n:CARDINAL;
	len: CARDINAL; S:ARRAY OF CHAR):BOOLEAN;
PROCEDURE DELETE(VAR S:ARRAY OF CHAR;p,n:CARDINAL);
PROCEDURE INSERT(VAR S:ARRAY OF CHAR; p:CARDINAL;
	S1: ARRAY OF CHAR);
PROCEDURE CAPS(S:ARRAY OF CHAR);
END STRINGS.
	This module deals with the manipulation of strings; i.e. Arrays of
characters. You should read the section of the tutorial on Array
types for further information. The function LENGTH(S) returns the
length of the string S. CONCAT(S1,S2,D) joins together the strings
S1 and S2, putting the resultant string into D. SLICE(S,P,L,R) takes
the substring of S of length L starting at position P, and puts it into
R. COPY(S,D) copies the string S to the string D. APPEND(D,S)
joins the string S to the end of the string D.
	The function COMPARE(S1,S2) compares S1 and S2 using the
ordering based on the ASCII values of the characters. It returns -1 if
S1 precedes S2, zero if they are equal, and 1 if S1 is after S2.
	The function INDEX1(S,C) returns the position of the first occurrence
of the character C in the string S, and the function INDEX(S,SS)
returns the position of the first occurrence of the string SS as a
substring of the string S. If the character or substring is not
contained within the string then 65535 is returned.
	The function SEARCH (P,n,len,S) searches for an occurrence of
the string S starting at address P, increasing P by  len  and
decreasing  n  by 1 after each comparison. Searching continues
until a match is found or  n  reaches zero. If a match is found then
the function returns TRUE.

					41

DEFINITION MODULE PLUS3IO;
PROCEDURE PLUS3IN(FILENO:SHORTCARD);
PROCEDURE PLUS3OUT(FILENO:SHORTCARD);
END PLUS3IO.

	Since the +3 does not allow input and output from files using
the normal Spectrum streams, the procedures STDIN and STDOUT
cannot be used to select such input. Instead the routines PLUS3IN
and PLUS30UT should be used. The file number should first have
been connected to a file using OPEN from the module DRIVES, as
with the other drive systems.

					42

DEFINITION MODULE DATA;
FROM SYSTEM IMPORT ADDRESS,WORD;
PROCEDURE SWAPBYTE(VAR A,B:WORD);
PROCEDURE CLEAR(V:ARRAY OF WORD);
PROCEDURE FILL(V:ARRAY OF WORD;X:WORD);
PROCEDURE FILL2(V:ARRAY OF WORD;N:CARDINAL);
PROCEDURE MOVE(SRC,DST:ADDRESS;NUM:CARDINAL);
PROCEDURE SEARCHA(VAR A:ADDRESS;VAR n:CARDINAL;
	I:CARDINAL;B:ADDRESS;Bsiz:CARDINAL):BOOLEAN;
PROCEDURE MATCH(A,B:ADDRESS;Bsiz:CARDINAL):BOOLEAN;
PROCEDURE SEARCH1(VAR A:ADDRESS;VAR n:CARDINAL;
	B:ADDRESS;Bsiz:CARDINAL):BOOLEAN;
PROCEDURE MEMPAGE(N:SHORTCARD);
TYPE SPROC=PROCEDURE(CARDINAL,CARDINAL);
	GPROC=PROCEDURE(CARDINAL,CARDINAL):BOOLEAN;
PROCEDURE QSORT(FIRST,LAST:CARDINAL;GRT:GPROC;
	SWAP:SPROC);
END DATA.
	The procedure SWAPBYTE can be used to exchange two single
byte variables. CLEAR will set all the bytes of a variable to 0, while
FILL will set them to the given value. FILL2 will fill the bytes in twos,
using the value of the given CARDINAL. MOVE(SRC,DST,NUM)
copies NUM bytes from the memory at address SRC to that at
address DST.
	The function SEARCHA searches for the block of data at
address B of length Bsiz. It starts at the address given in A, then
increases A by I bytes and decreases  n  by 1 after each comparison. It
continues until successful, or until  n  reaches zero. If successful the
value TRUE is returned. Typically this routine would be used to
search through an array of records, for the record where a certain
field has a given value.
	The function SEARCH1 does the same as SEARCHA, for the
particular case when I is 1. This can be used to search through a
block of memory for a given pattern of bytes.
	The procedure QSORT(FIRST,LAST,GRT,SWAP) sorts elements
FIRST to LAST of an array of data. GRT(i,j) must return TRUE if
element i should be positioned after element j. SWAP(i,j) must swap
elements i and j of the array. These procedures must be provided
by the calling program.

					43

DEFINITION MODULE HARDWARE;
VAR fire:BOOLEAN;
	jsbyte:BITSET; direction:SHORTCARD;
PROCEDURE JOYSTICK(n:SHORTCARD):BOOLEAN;
PROCEDURE In(P:CARDINAL):SHORTCARD;
PROCEDURE Out(P:CARDINAL;X:SHORTCARD);
PROCEDURE KEYSCAN;
PROCEDURE LOCK;
PROCEDURE UNLOCK;
PROCEDURE SETINTR(IP:PROC);
PROCEDURE IM1;
PROCEDURE WAITINT;
PROCEDURE EI;
PROCEDURE DI;
PROCEDURE TESTRES(B:BOOLEAN):BOOLEAN;
END HARDWARE.
	The function JOYSTICK is used to read a joystick or
corresponding directions from the keyboard. The number n specifies the
joystick type/keys used. For n=0 these are the
cursor keys and space bar for fire. Sinclair      3     2     1
joysticks are specified by n=1 or n=2, and          \   |   /
Kempston compatible joysticks by n=3. If the          \ | /
joystick is moved then JOYSTICK(n) will return  4 <---- + ---->0
TRUE and the variable direction will be set           / | \
according to the diagram. The variable fire         /   |   \
indicates whether the fire button was being       5     6     7
pressed when JOYSTICK was last called. The bits of the variable
jsbyte are set as follows: 0 - fire, 1 - up, 2 - down, 3 -right, 4 -
left.
	The procedures In and Out are the same as the corresponding
Basic commands.
	The procedures EI, DI, LOCK, UNLOCK, TESTRES and IM1 are
described in the section on concurrent programmming. SETINTR
installs the given procedure as an interrupt routine. The procedure
needs to be written in machine code, as it must restore any registers
it uses, so it is usually easier to follow the method using
IOTRANSFER instead. WAITINT simply waits for the next interrupt to
occur. The procedure KEYSCAN does a scan of the keyboard, as
the Spoctrum normally does every 20 milliseconds. This may be
needed if you write your own interrupt routine.

					44

DEFINITION MODULE BASIC;
PROCEDURE RANDOM():CARDINAL;
PROCEDURE RANDOMIZE(SEED:CARDINAL);
PROCEDURE RND():REAL3;
PROCEDURE BEEP(PITCH,TIME:REAL);
PROCEDURE PAUSE(TICKS:CARDINAL);
PROCEDURE PLOT(X,Y:REAL);
PROCEDURE DRAW(X,Y:REAL);
PROCEDURE CIRCLE(X,Y,R:REAL);
PROCEDURE ARC(X,Y,Z:REAL);
PROCEDURE USR(X:CARDINAL):CARDINAL;
PROCEDURE BORDER(COLOUR:SHORTCARD);
VAR [0BD8FH] RETVAL:CARDINAL;
PROCEDURE FNARG(N:SHORTCARD):REAL;
PROCEDURE FNARGSTR(VAR S:ARRAY OF CHAR;
	N:SHORTCARD);
END BASIC.
	The function RANDOM() returns a random cardinal number,
generated using the same algorithm as in Basic (but much quicker!).
RANDOMIZE sets the seed of the random number generator. RND()
returns a random number of type REAL3 in the range 0 to 1.
	The procedures BEEP, PAUSE, PLOT, DRAW, CIRCLE, USR and
BORDER correspond to Basic commands; in fact, they call the
routines in the Basic ROM. ARC gives the 3 parameter draw. Note
that for fast graphics you should use the modules GRAPH1,
GRAPH2 and GRAPH3.
	The routines FNARG and FNARGSTR allow the Modula-2
program to receive data from the calling Basic program. To do this
the USR 48500 call should be put in a DEF FN command;
e.g. 1 DEF FN F(X,Y,N$)=USR 48500. Then a statement such
as LET L=FN F(2.5,20,"TEST") would call the compiled program.
Within the Modula-2 program the function FNARG(1) will return 2.5
and FNARG(2) will return 20.0. The procedure FNARGSTR(S,3) will
put the string "TEST" into the variable S.
	The value in the variable RETVAL is that returned by the USR
48500 call on return to Basic. Thus if the statement RETVAL:=64
was in the Modula-2 program in the above example then 64 would
be assigned to the Basic variable L.

					45

DEFINITION MODULE ERRORS;
PROCEDURE ERROR(N:SHORTCARD);
PROCEDURE ERTRAP(P:PROC):SHORTCARD;
PROCEDURE EXCEPTION(N:SHORTCARD);
PROCEDURE EXTRAP(P:PROC):SHORTCARD;
PROCEDURE BREAK():BOOLEAN;
VAR [0BD03H]
ERCTRL,RERRNO,DRVERR:SHORTCARD;
END ERRORS.

	The procedure ERROR(N) generates the Spectrum error with
error number N. This will normally cause an exit from the program,
and the appropriate error message to be displayed. However, the
function ERTRAP can be used to trap the Spectrum errors. This
executes the procedure passed to it as a parameter, and if an error
occurs during this execution then a return is made from the ERTRAP
function with the error number as the function result. Note that this
will not usually work with drive errors which do not use the Spectrum
error routines.
	The procedures EXCEPTION and EXTRAP act in a similar way to
ERROR and ERTRAP with a return from EXTRAP being caused by a
call to EXCEPTION. The function BREAK() returns TRUE if the
BREAK key is being pressed when it is called.
	The variable ERCTRL allows control over what happens when a
run-time error occurs. Normally this is zero. If it is set to 1 then
errors are ignored, if 2 the Spectrum error handler will be called
directly. (3 is as 2 but without the position of the error being stored).
After an error RERRNO will hold the number of the error. Following a
drive error the contents of the A register will be put in DRVERR, as
this may indicate what sort of error has occured.

DEFINITION MODULE SETOPS;
PROCEDURE UNION(S1,S2,SOUT:ARRAY OF WORD);
PROCEDURE INTERSECT(S1,S2,SOUT:ARRAY OF WORD);
PROCEDURE DIFFER(S1,S2,SOUT:ARRAY OF WORD);
PROCEDURE SDIFF(S1,S2,SOUT:ARRAY OF WORD);
PROCEDURE COUNT(S:ARRAY OF WORD):CARDINAL;
END SETOPS.
	As the compiler does not implement the set operations +, *, -,
/ for sets with more than 16 values in the base type, this module
gives procedures which will perform these operations. The
procedure COUNT returns the number of elements in a set.

					46

DEFINITION MODULE STORAGE;
FROM SYSTEM IMPORT ADDRESS;
PROCEDURE ALLOCATE(a:ADDRESS;size:CARDINAL);
PROCEDURE DEALLOCATE(a:ADDRESS;size:CARDINAL);
PROCEDURE MARK;
PROCEDURE RELEASE;
PROCEDURE Available(size:CARDINAL):BOOLEAN;
PROCEDURE SETHEAP(Start,End1:ADDRESS);
END STORAGE.

	This module is concerned with allocating storage of dynamic
variables on the "heap". If T is a type and P a variable of type
POINTER TO T then ALLOCATE(P,TSIZE(T)) creates an
anonymous variable of type T and makes P point to it, so that it can
be referenced as P^. When the variable referenced by P^ is no
longer required, DEALLOCATE(P,TSIZE(T)) will make its storage
space available on the heap again. However, this is only the case if
P^ refers to the variable on the top of the heap; i.e. the most
recently created dynamic variable. An alternative way of dealing
with heap storage is to call MARK before creating variables on the
heap. A call of RELEASE reclaims the space taken since the call of
MARK.
	The function Available(size) returns TRUE if there is space to
allocate  size  bytes on the heap.
	The procedure SETHEAP can be used to create a new heap in
a different place, delimited by the addresses in Start and End1 . If
a new heap is created, then any dynamic variables on the old heap
may still be accessed, but any new dynamic variables will be
created on the new heap.

					47

DEFINITION MODULE GRAPH0;
CONST width=256; height=192;
PROCEDURE SCRDOT;
PROCEDURE SCRNAD;
PROCEDURE CLS2;
PROCEDURE GCLS;
PROCEDURE SETATTRS(X,Y:CARDINAL;L,C:SHORTCARD);
END GRAPH0.
	This module provides the elementary procedures used by the
other graphics modules. CLS2 clears the screen, but not the
attributes area, while GCLS clears the screen and attributes.
SETATTRS(X,Y,L,C) sets L character squares to have attribute C
starting at character position (X,Y). SCRDOT and SCRNAD are
imported by other graphic modules, and are not useable as
procedures on their own.

DEFINITION MODULE GRAPH1;
FROM SYSTEM IMPORT WORD;
VAR HLINE:PROCEDURE(INTEGER,INTEGER,INTEGER);
	PLOT,LINETO:PROCEDURE (INTEGER,INTEGER);
	AREA: PROCEDURE(INTEGER,INTEGER,INTEGER,INTEGER);
PROCEDURE QLINE(N,D:INTEGER);
PROCEDURE SETPOS(X,Y:INTEGER);
PROCEDURE POINT(X,Y:INTEGER):BOOLEAN;
PROCEDURE SETMODE(C:CARDINAL);
PROCEDURE SETMASK(W:WORD);
PROCEDURE SETBRKN(SOLID,GAP:CARDINAL);
PROCEDURE DHLINE(X1,Y,X2:INTEGER);
PROCEDURE DPLOT(X,Y:INTEGER);
PROCEDURE DLINETO(X,Y:INTEGER);
PROCEDURE DAREA(x,y,dx,dy:INTEGER);
END GRAPH1.

	This module defines the routines upon which graphics are
based. HLINE(X1,Y,X2) draws a line from (X1,Y) to (X2,Y).
PLOT(X,Y) plots a dot at (X,Y), and sets the plotting position there.
LINETO(X,Y) draws a line from the current plotting       3     2     1
position to (X,Y) and moves the plotting position.         \   |   /
AREA(X,Y,DX,DY) fills the rectangular area of size DX        \ | /
by DY whose bottom left corner is at (X,Y),            4 <---- + ---->0
	QLINE(N,D) is a quick way of drawing a line in         / | \
one of 8 directions, where N is the length in pixels,      /   |   \
                                                         5     6     7

					48

and D specifies the direction (see diagram.)
	SETPOS sets the plotting position to (X,Y) without plotting
anything, and POINT(X,Y) returns TRUE if the pixel at position (X,Y)
is filled.
	SETMODE allows the setting of the graphics mode as follows:
	Mode 0 - unplot (i.e. plotting in white ).
	Mode 1 - normal plotting.
	Mode 2 - Xor mode (i.e. replotting a pixel resets it to white ).
	Mode 5 - plotting a broken line for LINETO, using a 1 byte pixel
mask for HLINE and AREA.
	Mode 4 - As mode 5, but plotting in white.
	SETMASK sets the mask for modes 4&5, and SETBRKN(SOLID,
GAP) sets the broken line to consist of SOLID pixels plotted followed by
GAP pixels not plotted.
	The procedures DHLINE, DPLOT, DLINETO and DAREA are the
actual procedures assigned to the procedure variables HLINE,
PLOT, LINETO and AREA.

DEFINITION MODULE GRAPH2;
PROCEDURE CIRCLE(X,Y,R:INTEGER);
PROCEDURE ELLIPSE(X,Y,RX,RY:INTEGER);
PROCEDURE FCIRCLE(X,Y,R:INTEGER);
PROCEDURE FELLIPSE(X,Y,RX,RY:INTEGER);
PROCEDURE FTRAPEZ(XL0,Y0,XR0,XL1,Y1,XR1:INTEGER);
PROCEDURE ARC0(FX,FY,THETA,OX,OY,X,Y,R,DUM:INTEGER);
PROCEDURE EARC0(FX,FY,THETA,OX,OY,X,Y,RX,RY:INTEGER);
END GRAPH2.

	CIRCLE(X,Y,R) draws a circle with centre at (X,Y), radius R.
ELLIPSE(X,Y,RX,RY) draws an ellipse with centre (X,Y), horizontal
semi-axis RX and vertical semi-axis RY. FCIRCLE and FELLIPSE
draw a filled circle and ellipse. FTRAPEZ draws a filled trapezium,
with base (XL0,Y0) to (XR0,Y0) and top (XL1,Y1) to (XR1,Y1). ARCO
and EARC0 are used by the module GRAPH3.

DEFINITION MODULE GRAPH3;
PROCEDURE ARC(CX,CY,R:INTEGER;TH1,TH2:REAL3);
PROCEDURE EARC(CX,CY,RX,RY:INTEGER;TH1,TH2:REAL3);
END GRAPH3.
	ARC(CX,CY,R,TH1,TH2) draws an arc of a circle centre (X,Y),
radius R, starting at angle TH1 radians with the horizontal, finishing
at angle TH2. EARC draws the corresponding arc of an ellipse.

					49

DEFINITION MODULE CLIPPING;
PROCEDURE ClipOn(X1,Y1,X2,Y2:INTEGER);
PROCEDURE ClipOff;
PROCEDURE INRANGE(X,Y:INTEGER):BOOLEAN;
(*
PROCEDURE ClipLINETO(X,Y:INTEGER);
PROCEDURE ClipHLINE(XL,Y,XR:INTEGER);
PROCEDURE ClipAREA(x,y,dx,dy:INTEGER);
PROCEDURE ClipPLOT(X,Y:INTEGER);
*)
END CLIPPING.

	This module must be imported into any program using the
GRAPH modules if any plotting refers to positions which are not
actually on the screen. It may also be used to clip graphics to the
rectangular portion of the screen whose lower left corner is at
(X1,Y1) and upper right corner is at (X2,Y2), using ClipOn(X1,Y1,
X2,Y2). The procedure ClipOff turns off clipping. The function
INRANGE(X,Y) returns TRUE if the point (X,Y) is within the clipping
rectangle. The procedures ClipLINETO etc, are commented out, as
normally they are assigned to the procedure variables LINETO etc.
of the module GRAPH1.
	Note that clipping only applies to routines such as those in
GRAPH2 and GRAPH3 using LINETO, HLINE, AREA, and PLOT; and
not to QLINE, or the modules SYMBOL (or FILL).

DEFINITION MODULE FILLING;
PROCEDURE FILL(X,Y:CARDINAL);
END FILLING.

	The procedure FILL provided by this module does a contour fill
from the point (X,Y) out to a boundary of any shape. Note that
the point must be completely surrounded, as the fill does not stop if
it reaches the edge of the screen, but continues and so corrupts
other parts of memory, causing a crash.

DEFINITION MODULE SYMBOL;
FROM SYSTEM IMPORT WORD;
PROCEDURE SYMBOL1(X,Y:INTEGER;A:ARRAY OF WORD;
	C:CARDINAL);
PROCEDURE SYMBOL2(X,Y:INTEGER;A:ARRAY OF CARDINAL;
	C:CARDINAL);
END SYMBOL.

					50

	This module allows the plotting of sprite-like symbols on the
screen. SYMBOL1(X,Y,A,C) will plot a symbol 8 pixels wide
centred on the pixel position (X,Y). The symbol is defined by the
bytes in array A, and the number of bytes in this array determines
the height of the symbol. C is the plotting mode (as in the module
GRAPH1), and can take values 0 (unplot), 1 (normal plotting) or 2
(xor plotting). SYMBOL2(X,Y,A,C) plots a symbol 16 pixels wide,
with the pairs of bytes required obtained from the CARDINAL values
in array A. These procedures do not use the graphics routines from
GRAPH1, and so are not affected by the CLIPPING module. They
will function correctly with the point (X,Y) off the screen, provided it
is within 64 pixels of the edge of the screen.

					51

DEFINITION MODULE NUMIO;
PROCEDURE STRTOCARD(S:ARRAY OF CHAR;
	VAR OK:BOOLEAN):CARDINAL;
PROCEDURE STRTOINT(S:ARRAY OF CHAR;
	VAR OK:BOOLEAN):INTEGER;
PROCEDURE SUBSTRTOCARD(S:ARRAY OF CHAR;
	VAR P:CARDINAL; VAR OK1:BOOLEAN):CARDINAL;
PROCEDURE SUBSTRTOINT(S:ARRAY OF CHAR;
	VAR P:CARDINAL; VAR OK1:BOOLEAN):INTEGER;
PROCEDURE INTTOSTR(X:INTEGER; VAR S:ARRAY OF CHAR;
	VAR OK:BOOLEAN);
PROCEDURE CARDTOSTR(X:CARDINAL;VAR S:ARRAY OF CHAR;
	VAR OK:BOOLEAN);
PROCEDURE WRCARD(X:CARDINAL;W:CARDINAL);
PROCEDURE WRCARD0(X:CARDINAL;W:CARDINAL);
PROCEDURE WRINT(X:INTEGER;W:CARDINAL);
PROCEDURE RDCARD():CARDINAL;
PROCEDURE RDINT():INTEGER;
END NUMIO.
	This module deals with the input and output of INTEGER or
CARDINAL numbers. This involves conversion between numbers
and strings, and the routines to do this are also made available. This
module imports routines from the module STDIO, so it will use
whatever method of input or output has been selected.
	WRCARD(X,W) prints the number X. If this takes less than W
characters, blanks are printed before the number to make up the
required length. W is called the field width. The corresponding
string conversion procedure is CARDTOSTR(X,S) which puts the
decimal representation of X into the string S. WRINT and INTTOSTR
do the same for INTEGER numbers. The procedure
WRCARD0(X,W) prints the number X but precedes it with 0s rather
than spaces to make up W characters.
	RDCARD() is a function which returns the value of the cardinal
number it reads from the input. This uses RDITEM from the module
STDIO, so input stops when a space is reached. The conversion
function STRTOCARD(S,OK) returns the CARDINAL number whose
decimal representation is in the string S, and sets OK to TRUE if this
is a valid representation of a number, and FALSE if it is not.
SUBSTRTOCARD(S,P,OK1) starts at position P of the string S, and
converts the characters after this position to a cardinal. For instance
if P started as 0 and S was "18:30:25" then the value returned would
be 3, and P would become 2. The parameter passed to OK1 is set

					52

to FALSE if no valid number can be formed, otherwise it is left as it
is. This means that if several substrings are being converted it can
be set to TRUE at the start, then tested at the end to check they all
were valid.
	The procedures RDINT(), STRTOINT and SUBSTRTOINT are the
corresponding procedures for numbers of type INTEGER.

	The following three modules deal with the input and output of
numbers of the three real types. They have identical form, so only
the one dealing with REAL5 will be looked at.

DEFINITION MODULE R2IO;
PROCEDURE R2TOSTRFIX(X:REAL2;D:CARDINAL;
	VAR S:ARRAY OF CHAR);
PROCEDURE R2TOSTR(X:REAL2;D:CARDINAL;
	VAR S:ARRAY OF CHAR);
PROCEDURE STRTOR2(S:ARRAY OF CHAR;
	VAR OK:BOOLEAN):REAL2;
PROCEDURE SUBSTRTOR2(S:ARRAY OF CHAR;
	VAR P:CARDINAL;VAR OK:BOOLEAN):REAL2;
PROCEDURE WRR2FIX(X:REAL2;W,D:CARDINAL);
PROCEDURE WRR2(X:REAL2;W,D:CARDINAL);
PROCEDURE RDR2():REAL2;
END R2IO.


DEFINITION MODULE R3IO;
PROCEDURE R3TOSTRFIX(X:REAL3;D:CARDINAL;
	VAR S:ARRAY OF CHAR);
PROCEDURE R3TOSTR(X:REAL3;D:CARDINAL;
	VAR S:ARRAY OF CHAR);
PROCEDURE STRTOR3(S:ARRAY OF CHAR;
	VAR OK:BOOLEAN):REAL3;
PROCEDURE SUBSTRTOR3(S:ARRAY OF CHAR;
	VAR P:CARDINAL;VAR OK:BOOLEAN):REAL3;
PROCEDURE WRR3FIX(X:REAL3;W,D:CARDINAL);
PROCEDURE WRR3(X:REAL3;W,D:CARDINAL);
PROCEDURE RDR3():REAL3;
END R3IO.

					53

DEFINITION MODULE R5IO;
PROCEDURE R5TOSTRFIX(X:REAL5;D:CARDINAL;
	VAR S:ARRAY OF CHAR);
PROCEDURE R5TOSTR(X:REAL5;D:CARDINAL;
	VAR S:ARRAY OF CHAR);
PROCEDURE STRTOR5(S:ARRAY OF CHAR;
	VAR OK:BOOLEAN):REAL5;
PROCEDURE SUBSTRTOR5(S:ARRAY OF CHAR;
	VAR P:CARDINAL;VAR OK:BOOLEAN):REAL5;
PROCEDURE WRR5FIX(X:REAL5;W,D:CARDINAL);
PROCEDURE WRR5(X:REAL5;W,D:CARDINAL);
PROCEDURE RDR5():REAL5;
END R5IO.
	WRR5FIX(X,W,D) prints the number X in fixed point form to D
decimal places, using field width W, whilst WRR5(X,W,D) prints in
floating point form. The function RDR5() inputs a number, which it
returns its result. The routines to convert between strings and real
numbers are also made available. R5TOSTR and R5TOSTRFIX
convert a number to a string, with a given number of decimal places.
STRTOR5 converts a string to a real number, and SUBSTRTOR5
converts a substring, as explained under NUMIO.

DEFINITION MODULE R2MATH;
PROCEDURE SIN(x:REAL2):REAL2;
PROCEDURE COS(x:REAL2):REAL2;
PROCEDURE TAN(x:REAL2):REAL2;
PROCEDURE ARCSIN(x:REAL2):REAL2;
PROCEDURE ARCCOS(x:REAL2):REAL2;
PROCEDURE ARCTAN(x:REAL2):REAL2;
PROCEDURE ARCTAN2(y,x:REAL2):REAL2;
PROCEDURE EXP(x:REAL2):REAL2;
PROCEDURE LN(x:REAL2):REAL2;
PROCEDURE SQRT(x:REAL2):REAL2;
PROCEDURE SQUARE(x:REAL2):REAL2;
PROCEDURE POWER(x,y:REAL2):REAL2;
PROCEDURE FRAC(X:REAL2):REAL2;
PROCEDURE ENTIER(x:REAL2):INTEGER;
(*
PROCEDURE HAV(X:REAL2):REAL2;
PROCEDURE ARCHAV(X:REAL2):REAL2;
*)
END R2MATH.

					54

DEFINITION MODULE R3MATH;
PROCEDURE SIN(x:REAL3):REAL3;
PROCEDURE COS(x:REAL3):REAL3;
PROCEDURE TAN(x:REAL3):REAL3;
PROCEDURE ARCSIN(x:REAL3):REAL3;
PROCEDURE ARCCOS(x:REAL3):REAL3;
PROCEDURE ARCTAN(x:REAL3):REAL3;
PROCEDURE ARCTAN2(y,x:REAL3):REAL3;
PROCEDURE EXP(x:REAL3):REAL3;
PROCEDURE LN(x:REAL3):REAL3;
PROCEDURE SQRT(x:REAL3):REAL3;
PROCEDURE SQUARE(x:REAL3):REAL3;
PROCEDURE POWER(x,y:REAL3):REAL3;
PROCEDURE FRAC(X:REAL3):REAL3;
PROCEDURE ENTIER(x:REAL3):INTEGER;
(*
PROCEDURE HAV(X:REAL3):REAL3;
PROCEDURE ARCHAV(X:REAL3):REAL3;
*)
END R3MATH.

DEFINITION MODULE R5MATH;
PROCEDURE SIN(x:REAL5):REAL5;
PROCEDURE COS(x:REAL5):REAL5;
PROCEDURE TAN(x:REAL5):REAL5;
PROCEDURE ARCSIN(x:REAL5):REAL5;
PROCEDURE ARCCOS(x:REAL5):REAL5;
PROCEDURE ARCTAN(x:REAL5):REAL5;
PROCEDURE ARCTAN2(y,x:REAL5):REAL5;
PROCEDURE EXP(x:REAL5):REAL5;
PROCEDURE LN(x:REAL5):REAL5;
PROCEDURE SQRT(x:REAL5):REAL5;
PROCEDURE FRAC(x:REAL5):REAL5;
PROCEDURE ENTIER(x:REAL5):INTEGER;
PROCEDURE SQUARE(x:REAL5):REAL5;
PROCEDURE POWER(x,y:REAL5):REAL5;
END R5MATH.
	The above three modules contain mathematical functions for
the three real types. You will probably be familiar with most of them.
ARCTAN2(Y,X) returns ARCTAN(Y/X) adjusted to be in the quadrant
determined by the signs of X and Y, in the range -pi to pi. SQRT(X)
returns the square root of X, and SQUARE(X) returns X^2.

					55

POWER(x,y) returns x^y (there is no operator for this in Modula-2).
ENTIER(X) returns the integer below X. Note that is the same as
the Spectrum Basic function INT, whereas the function INT in this
compiler truncates towards zero. For example ENTIER(-2.3) is -3,
and INT(-2.3) is -2. FRAC(X) returns the fractional part of X, so
FRAC(-2.3) is -0.3.
	The function HAV(X) is equal to (1-COS(X))/2, known as the
haversine function. Some trigonometrical formulae may be
expressed in terms of haversines, and doing so may reduce the
loss of accuracy, which is important for the types REAL2 and REAL3.
ARCHAV is the inverse function of HAV. Note that the comment
brackets need to be removed if these functions are to be used.


				6:General information.
	Much testing has been done on this compiler to ensure that it
functions correctly. However, it must be understood that any such
product is likely to contain bugs, and Mira Software accepts no
responsibility for any loss arising from the use of this product. If you
do have a problem which appears to be due to a bug in the
compiler, you are encouraged to report it to Mira Software. Try to
be as specific as you can, and if possible send the source program
with which the bug occurs. Mira Software will try to solve the
problem, and if successful then you will be sent a debugged version.

	All software and documentation in this product is copyright.
You may make a copy of the software for back-up purposes, but
making a copy to give or sell to someone else is illegal. The
run-time drivers are an exception to this, and may be included with
compiled programs for distribution to other people. There are no
restrictions on the distribution of the compiled code of programs you
have written yourself.

					56
