8.4 Extended Low Level Examples

8.4.1 Keyboard Reading--Operating System Level

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.

8.4.2 Generic Swap

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.

Contents