[comp.lang.modula2] Generic Lists in Modula-2

Peter.M..Perchansky@f101.n273.z1.fidonet.org (Peter M. Perchansky) (08/09/90)

DEFINITION MODULE PMPDeque;

(*---------- Procedures from JPI's TopSpeed Modula II -------------*)
FROM SYSTEM IMPORT BYTE;
(*-----------------------------------------------------------------*)

TYPE
    Deques;           (* opaque type defined in implementation module *)
    DequesStatus   = (empty, full, mismatchedSize, noSuchNode, none);
    ErrorHandler   = PROCEDURE (DequesStatus);

(*  DequesStatus code explainations: *)
(*    empty:           An operation tried to remove/show data from *)
(*                     an empty deque. *)
(*    full:            An operation tried to add data to a full *)
(*                     deque; no more memory available. *)
(*    mismatchedSize:  An operation tried to remove/show data to a *)
(*                     variable whose size (in bytes) did not match *)
(*                     the size of the variable stored in the deque. *)
(*    noSuchNode:      An operation tried to process a node that does *)
(*                     not exist in the deque. *)
(*    none:            No error occured during processing. *)

PROCEDURE CreateDeque (VAR deque: Deques);
(* Creates the deque by making it NIL *)
(* This procedure must be called prior to performing operations on *)
(* the deque. *)

PROCEDURE Empty (deque: Deques): BOOLEAN;
(* Return TRUE if the deque is NIL (empty). *)

PROCEDURE DestroyDeque (VAR deque: Deques);
(* Destroys the deque by deallocating deque nodes and contents *)
(* DestroyDeque should be called when the deque is no longer needed *)
(* in memory. *)

PROCEDURE Full (size: CARDINAL): BOOLEAN;
(* Return TRUE if there is no Available memory for another deque node *)

PROCEDURE DequeLength (deque: Deques): CARDINAL;
(* Returns the number of nodes in the deque *)

PROCEDURE DequePos (data: ARRAY OF BYTE; deque: Deques): CARDINAL;
(* Returns the number of the node containing the data (if found). *)
(* Returns 0 if not found.  The front of the deque is treated as node *)
(* number one. *)

PROCEDURE InDeque (data: ARRAY OF BYTE; deque: Deques): BOOLEAN;
(* Returns TRUE if data is found in deque. *)

PROCEDURE LastError (): DequesStatus;
(* Returns the status code containing the last error (if any) from a *)
(* previous operation. *)

PROCEDURE InstallErrorHandler (handler: ErrorHandler);
(* Installs client-module error procedure to handle errors that occur *)
(* during deque operations.  Until an error-handler is installed, all *)
(* errors will be ignored. *)

PROCEDURE Enqueue (data: ARRAY OF BYTE; VAR deque: Deques);
(* Place data in node at the back of the deque.  status is set to *)
(* full or none. *)

PROCEDURE Push (data: ARRAY OF BYTE; VAR deque: Deques);
(* Place data in node at the back of the deque.  status is set to *)
(* full or none. *)

PROCEDURE Dequeue (VAR data: ARRAY OF BYTE; VAR deque: Deques);
(* Place contents from node at the front of the deque into data. *)
(* Remove the front node of the deque.  status is set to empty, *)
(* mismatchedSize, or none. *)

PROCEDURE Pop (VAR data: ARRAY OF BYTE; VAR deque: Deques);
(* Place contents from node at the back of the deque into data. *)
(* Remove the back node of the deque.  status is set to empty, *)
(* mismatchedSize, or none. *)

PROCEDURE Update (data: ARRAY OF BYTE; nthItem: CARDINAL; VAR deque: Deques);
(* Update contents of nth node of the deque from data.  status is set *)
(* to empty, mismatchedSize, noSuchNode, or none. *)

PROCEDURE Serve (VAR data: ARRAY OF BYTE; nthItem: CARDINAL; VAR deque: Deques);
(* Place contents from nth node of the deque into data.  Remove the *)
(* nth node of the deque.  status is set to empty, mismatchedSize, *)
(* noSuchNode, or none. *)

PROCEDURE Front (VAR data: ARRAY OF BYTE; deque: Deques);
(* Place contents from node at the front of the deque into data. *)
(* status is set to empty, mismatchedSize, or none. *)

PROCEDURE Back (VAR data: ARRAY OF BYTE; deque: Deques);
(* Place contents from node at the back of the deque into data. *)
(* status is set to empty, mismatchedSize, or none. *)

PROCEDURE Top (VAR data: ARRAY OF BYTE; deque: Deques);
(* Place contents from node at the back (top) of the deque into data. *)
(* status is set to empty, mismatchedSize, or none. *)

PROCEDURE Retrieve (VAR data: ARRAY OF BYTE; nthItem: CARDINAL; deque: Deques);
(* Place contents from nth node of the deque into data.  status is *)
(* set to empty, mismatchedSize, noSuchNode, or none. *)

END PMPDeque.


--  
uucp: uunet!m2xenix!puddle!273!101!Peter.M..Perchansky
Internet: Peter.M..Perchansky@f101.n273.z1.fidonet.org

Peter.M..Perchansky@f101.n273.z1.fidonet.org (Peter M. Perchansky) (08/09/90)

IMPLEMENTATION MODULE PMPDeque;

(*---------- Procedures from JPI's TopSpeed Modula II -------------*)
FROM Lib      IMPORT Compare;
FROM SYSTEM   IMPORT ADDRESS, BYTE, SIZE;
FROM Storage  IMPORT ALLOCATE, Available, DEALLOCATE;
(*-----------------------------------------------------------------*)

(*---------- Procedures from PMP's Library Modules ----------------*)
FROM PMPLow   IMPORT _Move;
(*-----------------------------------------------------------------*)

TYPE
    Deques     = POINTER TO DequeNodes;  (* opaque type defined *)

    DequeNodes = RECORD                  (* node *)
                   contents : ADDRESS;   (* contents of node *)
                   size     : CARDINAL;  (* size of contents *)
                   next     : Deques;    (* pointer to next node *)
                   previous : Deques;    (* pointer to prev node *)
                 END;

(*-----------------------------------------------------------------*)
(*          Global Variables used internally by PMPDeque.          *)
(*-----------------------------------------------------------------*)

VAR
    CallError : ErrorHandler;  (* client-module installed error proc *)
    lastError : DequesStatus;  (* set by procedures when error occurs *)

(*-----------------------------------------------------------------*)
(*          Utility procedures used internally by PMPDeque.        *)
(*-----------------------------------------------------------------*)

PROCEDURE DeleteNode (VAR node: Deques);

VAR
    nodeToDelete : Deques;             (* save pointer for deletion *)

BEGIN
    IF node^.next = node THEN          (* Delete last node in deque *)
       DEALLOCATE (node^.contents, node^.size);
       DEALLOCATE (node, SIZE (DequeNodes));
       node := NIL;
    ELSE
       nodeToDelete := node;           (* Delete specified node     *)
       node := node^.next;
       nodeToDelete^.previous^.next := node;
       node^.previous := nodeToDelete^.previous;
       DEALLOCATE (nodeToDelete^.contents, nodeToDelete^.size);
       DEALLOCATE (nodeToDelete, SIZE (DequeNodes));
    END;
END DeleteNode;

PROCEDURE InsertNodeAtEnd (node: Deques; VAR deque: Deques);

BEGIN
    IF Empty (deque) THEN              (* create front *)
       node^.next := node;
       node^.previous := node;
       deque := node;
    ELSE                               (* add to end   *)
       node^.next := deque;
       node^.previous := deque^.previous;
       deque^.previous^.next := node;
       deque^.previous := node;
    END;
END InsertNodeAtEnd;

PROCEDURE SetError (status: DequesStatus);

BEGIN
    lastError := status;
    CallError (status);
END SetError;

PROCEDURE IgnoreError (status: DequesStatus);

BEGIN
END IgnoreError;

(*-----------------------------------------------------------------*)
(*          Procedures exported by PMPDeque.                       *)
(*-----------------------------------------------------------------*)

PROCEDURE CreateDeque (VAR deque: Deques);

BEGIN
    deque := NIL;
END CreateDeque;

PROCEDURE Empty (deque: Deques) : BOOLEAN;

BEGIN
    RETURN deque = NIL;
END Empty;

PROCEDURE DestroyDeque (VAR deque: Deques);

BEGIN
    WHILE NOT Empty (deque) DO
          DeleteNode (deque);
    END;
END DestroyDeque;

PROCEDURE Full (size: CARDINAL): BOOLEAN;

BEGIN
    RETURN (NOT (Available (SIZE (DequeNodes) + size)));
END Full;

PROCEDURE DequeLength (deque : Deques) : CARDINAL;

VAR
    current : Deques;           (* cursor used to walk the deque *)
    count   : CARDINAL;         (* node counter *)

BEGIN
    count := 0;

    IF NOT Empty (deque) THEN
       current := deque^.previous;        (* start from back of deque *)

       REPEAT
             INC (count);
             current := current^.next;    (* and walk forward until *)
       UNTIL current = deque^.previous;   (* we've reached the back *)
    END;

    RETURN count;
END DequeLength;

PROCEDURE DequePos (data: ARRAY OF BYTE; deque: Deques): CARDINAL;

VAR
    count,                      (* node counter *)
    pos      : CARDINAL;        (* returned from AsmLib.Compare  *)
    current  : Deques;          (* cursor used to walk the deque *)

BEGIN
    IF NOT Empty (deque) THEN
       count := 0;
       current := deque^.previous;    (* start from the back of deque *)

       REPEAT
            INC (count);
            current := current^.next;
            pos := Compare (ADR (data), current^.contents, current^.size);
       UNTIL (current = deque^.previous) OR (pos = current^.size);

       IF pos = current^.size THEN     (* pos = size upon exact match *)
          RETURN count
       ELSE
          RETURN 0
       END;
    ELSE
       RETURN 0
    END;
END DequePos;

PROCEDURE InDeque (data: ARRAY OF BYTE; deque: Deques): BOOLEAN;

BEGIN
    RETURN (DequePos (data, deque) # 0);
END InDeque;

PROCEDURE LastError (): DequesStatus;

BEGIN
    RETURN lastError;
END LastError;

PROCEDURE InstallErrorHandler (handler: ErrorHandler);

BEGIN
    CallError := handler;
END InstallErrorHandler;

PROCEDURE Enqueue (data: ARRAY OF BYTE; VAR deque: Deques);
VAR
    newNode     : Deques;       (* used to create new node *)
    dataSize    : CARDINAL;     (* size of data *)

BEGIN
    dataSize := HIGH (data) + 1;

    IF NOT Full (dataSize) THEN
       ALLOCATE (newNode, SIZE (DequeNodes));
       ALLOCATE (newNode^.contents, dataSize);
       _Move (ADR (data), newNode^.contents, dataSize);
       newNode^.size := dataSize;
       InsertNodeAtEnd (newNode, deque);
    ELSE
       SetError (full);
    END;
END Enqueue;

PROCEDURE Push (data: ARRAY OF BYTE; VAR deque: Deques);

BEGIN
    Enqueue (data, deque);
END Push;

PROCEDURE Dequeue (VAR data: ARRAY OF BYTE; VAR deque: Deques);

VAR
    dataSize    : CARDINAL;     (* size of data *)

BEGIN
    IF NOT Empty (deque) THEN
       dataSize := HIGH (data) + 1;

       IF deque^.size = dataSize THEN
          _Move (deque^.contents, ADR (data), deque^.size);
          DeleteNode (deque);
       ELSE
          SetError (mismatchedSize)
       END;
    ELSE
       SetError (empty);
    END;
END Dequeue;

PROCEDURE Pop (VAR data: ARRAY OF BYTE; VAR deque: Deques);

VAR
  dataSize   : CARDINAL;        (* size of data *)

BEGIN
    IF NOT Empty (deque) THEN
       dataSize := HIGH (data) + 1;

       IF deque^.next = deque THEN          (* only one node *)
          IF deque^.size = dataSize THEN
             _Move (deque^.contents, ADR (data), deque^.size);
             DeleteNode (deque);
          ELSE
             SetError (mismatchedSize)
          END;
       ELSIF deque^.previous^.size = dataSize THEN
             _Move (deque^.previous^.contents, ADR (data), deque^.previous^.size);
             DeleteNode (deque^.previous);
          ELSE
             SetError (mismatchedSize)
          END;
    ELSE
       SetError (empty)
    END;
END Pop;

PROCEDURE Update (data: ARRAY OF BYTE; nthItem: CARDINAL; VAR deque: Deques);
VAR
    current        : Deques;    (* used to walk the deque *)
    dataSize,                   (* size of data *)
    numberOfNodes,              (* length of deque *)
    count          : CARDINAL;  (* node counter *)

BEGIN
    IF NOT Empty (deque) THEN
       numberOfNodes := DequeLength (deque);

       IF (nthItem <= numberOfNodes) AND (nthItem > 0) THEN
          count := 0;
          current := deque^.previous;      (* start from back of deque *)

          REPEAT
                INC (count);               (* walk from back until we *)
                current := current^.next;  (* have the nth Item *)
          UNTIL count = nthItem;

          dataSize := HIGH (data) + 1;

          IF current^.size = dataSize THEN
             _Move (ADR (data), current^.contents, current^.size)
          ELSE
             SetError (mismatchedSize)
          END;
       ELSE
          SetError (noSuchNode)
       END;
    ELSE
       SetError (empty)
    END;
END Update;

PROCEDURE Serve (VAR data: ARRAY OF BYTE; nthItem: CARDINAL; VAR deque: Deques);

VAR
    current        : Deques;    (* used to walk the deque *)
    dataSize,                   (* size of data *)
    numberOfNodes,              (* length of deque *)
    count          : CARDINAL;  (* node counter *)

BEGIN
    IF NOT Empty (deque) THEN
       numberOfNodes := DequeLength (deque);

       IF (nthItem <= numberOfNodes) AND (nthItem > 0) THEN
          count := 0;
          current := deque^.previous;      (* start from back of deque *)

          REPEAT
                INC (count);               (* walk from back until we *)
                current := current^.next;  (* have the nth Item *)
          UNTIL count = nthItem;

          dataSize := HIGH (data) + 1;

          IF current^.size = dataSize THEN
             _Move (current^.contents, ADR (data), current^.size);

             IF count = 1 THEN
                DeleteNode (deque)
             ELSE
                DeleteNode (current)
             END;
          ELSE
             SetError (mismatchedSize)
          END;
       ELSE
          SetError (noSuchNode)
       END;
    ELSE
       SetError (empty)
    END;
END Serve;

PROCEDURE Front (VAR data: ARRAY OF BYTE; deque: Deques);

VAR
    dataSize : CARDINAL;        (* size of data *)

BEGIN
    IF NOT Empty (deque) THEN
       dataSize := HIGH (data) + 1;

       IF deque^.size = dataSize THEN
          _Move (deque^.contents, ADR (data), deque^.size)
       ELSE
          SetError (mismatchedSize)
       END;
    ELSE
       SetError (empty)
    END;
END Front;

PROCEDURE Back (VAR data: ARRAY OF BYTE; deque: Deques);

VAR
    dataSize : CARDINAL;        (* size of data *)

BEGIN
    IF NOT Empty (deque) THEN
       dataSize := HIGH (data) + 1;

       IF deque^.previous^.size = dataSize THEN
          _Move (deque^.previous^.contents, ADR (data), deque^.previous^.size)
       ELSE
          SetError (mismatchedSize)
       END;
    ELSE
       SetError (empty)
    END;
END Back;

PROCEDURE Top (VAR data: ARRAY OF BYTE; deque: Deques);

BEGIN
    Back (data, deque);
END Top;

PROCEDURE Retrieve (VAR data: ARRAY OF BYTE; nthItem: CARDINAL; deque: Deques);

VAR
    current        : Deques;    (* used to walk the deque *)
    dataSize,                   (* size of data *)
    numberOfNodes,              (* length of deque *)
    count          : CARDINAL;  (* node counter *)

BEGIN
    IF NOT Empty (deque) THEN
       numberOfNodes := DequeLength (deque);

       IF (nthItem <= numberOfNodes) AND (nthItem > 0) THEN
          count := 0;
          current := deque^.previous;      (* start from back of deque *)

          REPEAT
                INC (count);               (* walk from back until we *)
                current := current^.next;  (* have the nth Item *)
          UNTIL count = nthItem;

          dataSize := HIGH (data) + 1;

          IF current^.size = dataSize THEN
             _Move (current^.contents, ADR (data), current^.size)
          ELSE
             SetError (mismatchedSize)
          END;
       ELSE
          SetError (noSuchNode)
       END;
    ELSE
       SetError (empty)
    END;
END Retrieve;

BEGIN                       (* initialization *)
    InstallErrorHandler (IgnoreError);
    lastError := none;
END PMPDeque.


--  
uucp: uunet!m2xenix!puddle!273!101!Peter.M..Perchansky
Internet: Peter.M..Perchansky@f101.n273.z1.fidonet.org

tensi@lan.informatik.tu-muenchen.dbp.de (Thomas Tensi) (08/17/90)

Hello Peter,

one problem with your deque module
(Peter.M..Perchansky@f101.n273.z1.fidonet.org's message of 9 Aug 90 03:42:22
GMT)
is that of storage reclamation for more complicated structures (e.g. deques
of deques).

Whenever you delete an element in your deque only the space of the element
itself is deallocated. This doesn't matter for data like records, arrays or
simple data types. But when this element is a pointer to something, the whole
information accessible from this pointer is lost and remains as garbage on the heap.

Of course you can say: "when I 'DestroyDeque' something, I know that the
elements involved are dynamic data structures and I deallocate them first".
When you do that everything is fine, but how about a callback procedure
installed when defining the deque? like that

          ...
  IMPORT SYSTEM;

  TYPE CallBackProcType = PROCEDURE (SYSTEM.ADDRESS);

  PROCEDURE CreateDeque (VAR deque: Deques;
                         CallBackProc : CallBackProcType);

          ...

This procedure is installed specific to any deque defined (just another
component of the deque record) and is called whenever an element from deque is
discarded (similar to your DEALLOCATE(node^.contents...)). So you get nearly
automatic storage reclamation without having to intersperse your code with
delete operations for your element data type.

The ugly thing about this solution is that SYSTEM is again used in the
definition module. But that's the way with genericity in MODULA-2...

Thomas Tensi

------------------------------------------------------------------------------
Thomas Tensi, Institut fuer Informatik, Technische Univ. Muenchen,
Arcisstr. 21, 8000 Muenchen 2, West Germany
        | E-Mail:
        | tensi@lan.informatik.tu-muenchen.dbp.de                (X.400)
        | tensi%lan.informatik.tu-muenchen.dbp.de@relay.cs.net   (arpa/csnet)
        | tensi%lan.informatik.tu-muenchen.dbp.de@unido.uucp     (uucp)
        | tensi%lan.informatik.tu-muenchen.dbp.de@ddoinf6.bitnet (bitnet)

Chip.Richards@p3.f18.n114.z1.fidonet.org (Chip Richards) (08/26/90)

Thomas,

 >   TYPE CallBackProcType = PROCEDURE (SYSTEM.ADDRESS);

Excellent idea.  Very similar to something we implemented in a compiler run-time some years ago, though ours was in assembly and not nearly as pretty!

 > The ugly thing about this solution is that SYSTEM is again used in the
 > definition module. But that's the way with genericity in MODULA-2...

Why wouldn't (VAR ARRAY OF WORD) work just as well, without being SYSTEM-dependent?

Chip


--  
uucp: uunet!m2xenix!puddle!114!18.3!Chip.Richards
Internet: Chip.Richards@p3.f18.n114.z1.fidonet.org