At a slightly higher level than that employed in the last section, it may be possible to take advantage of one's knowledge of low level procedures that have been provided in the operating system interface to read the keyboard. This technique is illustrated by the following library module:
DEFINITION MODULE Keyboard; (* ========================================= Definition and Implementation © 1993 by R. Sutcliffe Trinity Western University 7600 Glover Rd., Langley, BC Canada V3A 6H4 e-mail: rsutc@twu.ca Last modification date 1997 10 06 =========================================== *) (* Note that this module functions independently of the Terminal, *) PROCEDURE BusyRead (VAR ch: CHAR); (* return character if one there, otherwise return 0C *) PROCEDURE Read (VAR ch: CHAR); (* return character *) END Keyboard. IMPLEMENTATION MODULE Keyboard; (* ========================================= Definition and Implementation © 1993 by R. Sutcliffe Last modification date 1997 10 06 =========================================== *) (* first implementation 1993 10 20 revised for p1 1994 05 18 modifierSet becomes modifiers characterCode becomes charCode Desk changes to Events 1995 09 14 1996 08 08 changed to WaitNextEvent as problems with translating GetNextEvent to C *) FROM Events IMPORT EventRecord, GetCaretTime, WaitNextEvent, keyDown, cmdKey, everyEvent; FROM SYSTEM IMPORT CAST; VAR theEvent : EventRecord; gSleep : INTEGER; PROCEDURE BusyRead (VAR ch: CHAR); (* return character if one is there, otherwise return 0C *) BEGIN IF WaitNextEvent (everyEvent, theEvent, gSleep, NIL) AND (theEvent.what = keyDown) AND NOT (cmdKey IN theEvent.modifiers) THEN ch := theEvent.charCode; ELSE; ch := 0C END; (* if WaitNextEvent *) END BusyRead; PROCEDURE ReadChar (VAR ch: CHAR); BEGIN REPEAT BusyRead (ch); UNTIL ch # 0C END ReadChar; BEGIN (* main *) gSleep := GetCaretTime (); (* set idle time used by WaitNextEvent *) END Keyboard.
A program could now implement a procedure to wait for a keypress before continuing processing by importing BusyRead and then proceeding much as does Read in the above, except not returning anything to the caller.
PROCEDURE Keypress; BEGIN REPEAT Keyboard.BusyRead (ch); UNTIL ch # 0C END Keypress;
While the code shown here to achieve this is necessarily implementation specific to the Macintosh, something similar should be possible under any operating system.
It is often necessary to swap the values of two variables, and on a number of occasions such procedures have been used in this text. Each time one writes something like:
PROCEDURE Swap (VAR x, y : REAL);
and each time it is necessary to swap two items of a type not previously used, the procedure must be rewritten, if ever so slightly. Using the low level facilities of Modula-2 , it is possible to write a swap procedure that can swap two items of any type presented to it.
A procedure that is capable of acting on any data regardless of type is said to be generic.
Generic procedures are written using the ARRAY OF LOC as parameter. Since this has an equivalent high level meaning on every machine, the result code ought to be portable, even if the low level nature of LOC is different. Of course, the items ought both to be of the same type or the results will be meaningless. Indeed, if the items are not at least of the same size, attempting to swap on a memory location by memory location basis could prove disastrous unless the swap stops at the end of the shorter item, and even then the results of such a swap are not likely to be meaningful. The library module below encapsulates a generic swap. It also employs HIGH to compute the number of LOCs occupied by the variable passed to it as a parameter.
DEFINITION MODULE Swaps; FROM SYSTEM IMPORT LOC; PROCEDURE CanSwap (a, b : ARRAY OF LOC) : BOOLEAN; (* Pre : None Post: if a and b are of the same size, returns true, else returns false *) PROCEDURE Swap (VAR a, b : ARRAY OF LOC); (* Pre : None, but does no size checking, so if they are not the same size, the result may be meaningless Post: Swaps the number of bytes of the smaller of the two arrays. *) END Swaps.
The Procedure CanSwap is only able to check on the size of the items, not whether they really are the same type. For its part, the procedure Swap does no checking; it just swaps as many bytes as it can. Here is the code:
IMPLEMENTATION MODULE Swaps; FROM SYSTEM IMPORT LOC; PROCEDURE CanSwap (a, b : ARRAY OF LOC) : BOOLEAN; (* Pre : None Post: if a and b are of the same size, returns true, else returns false *) BEGIN RETURN (HIGH (a) = HIGH (b)); END CanSwap; PROCEDURE Swap (VAR a, b : ARRAY OF LOC); (* Pre : None, but does no size checking, so if they are not the same size, the result may be meaningless Post: Swaps the number of bytes of the smaller of the two arrays. *) VAR temp : LOC; max, count : CARDINAL; BEGIN max := HIGH (a); IF max > HIGH (b) THEN (* use lesser of two for # to swap *) max := HIGH (b); END; FOR count := 0 TO max DO temp := a [count]; a [count] := b [count]; b [count] := temp; END; END Swap; END Swaps.