Conference of Applied Mathematics - University of Central Oklahoma - Spring 1992



                       An Introduction to
            Turbo Pascal Object Oriented Programming

                        by Bill McDaniel


Records
-------

A record consists of 1 or more components, called 'field lists', that can be different types.
Each field list is a comma-separated list of identifiers, followed by a colon and a type.  A
sample record (with 6 fields) is shown below.

   -----------------------
  | record                |
  |   a : integer;        |
  |   b : real;           |
  |   c,d,e,f : string    |
  | end;                  |
   -----------------------


Objects
-------

An object is a data structure that contains (probably) non-homogeneous data fields.
Also, and more importantly, objects contain method headers that declare methods
(procedures and functions) that are used to access the fields within the object.

   ----------------------------------------
  | type                                   |
  |   MyObj = object                       |
  |     x,y : Integer;                     |
  |     constructor Init(i,j : integer);   |
  |     procedure   Display_Values;        |
  |     destructor  Done;                  |
  |   end;                                 |
   ----------------------------------------

In the above example, x and y are the fields in the record.  Init is the required constructor
for the object, and Done is the destructor.  The constructor has to be invoked before the
object can be referenced.  The destructor should be invoked when you are through with
the object.  In this example, there is just one other procedure in the object, and that is the
procedure Display_Values.

One can instantiate the object with a variable declaration.

   ------------------------
  | var                    |
  |   My_Object : MyObj;   |
  |                        |
   ------------------------


In the above statement, My_Object is an instance of MyObj.  All of the fields and methods
are referenced using, either, the dot notation...


   ------------------------------
  | My_Object.Init(0,0);         |
  | My_Object.x := 0;            |
  | My_Object.y := 0;            |
  | My_Object.Display_Values;    |
  | My_Object.Done;              |
   ------------------------------

... or the Pascal 'with' statement.

   -----------------------
  | with My_Object do     |
  |   begin               |
  |     Init(0,0);        |
  |     x := 0;           |
  |     y := 0;           |
  |     Display_Values;   |
  |     Done;             |
  |   end;                |
   -----------------------

The statements:  x := 0;  and  y := 0;  are usually frowned upon in finer oop circles,
because the pholosophy of oop dictates that the variables in the object should be
referenced ONLY by the methods, and not referenced directly.

The code for the methods is declared AFTER the object declaration.  Each procedure or
function name is prefixed with the object name.  The following is a complete program
that defines, then uses, the object 'MyObj'.

   ---------------------------------------
  | program example (input,output);       |   MYOBJ.PAS
  | type                                  |   create the object type
  |   MyObjPtr = ^ MyObj                  |
  |   MyObj = object                      |
  |     x,y : Integer;                    |
  |     constructor Init(i,j : integer);  |
  |     procedure   Display_Values;       |
  |     destructor  Done;                 |
  |   end;                                |
  |                                       |
  |   constructor MyObj.Init;             |   list the code associated with
  |     begin                             |   each method
  |       x := i;                         |
  |       y := j                          |
  |     end;                              |
  |   procedure MyObj.Display_Values;     |
  |     begin                             |
  |       writeln('x = ',x,'  y = ',y);   |
  |     end;                              |
  |   destructor MyObj.Done;              |
  |     begin                             |
  |     end;                              |
  | var                                   |
  |   My_Object : MyObj;                  |   create the instance
  |                                       |
  | begin                                 |
  |   with My_Object do                   |   reference the object
  |     begin                             |
  |       Init(5,4);                      |
  |       Display_Values;                 |
  |       Done;                           |
  |     end;                              |
  | end.                                  |
   ---------------------------------------


Pointer Variables and the NEW command
-------------------------------------

For dynamic allocation of variables (and objects), Standard Pascal provides variables
called 'pointer variables' that contain either the value 'nil' or the address of the
designated entity.  The declaration is straightforward.

   --------------------
  | var                |
  |   p : ^ sometype;  |
   --------------------

The above statement declares p to be a pointer ('^') to some type that has been previously
declared.

Example:
   ------------------------------------
  | type                               |
  |   ar = array [1..10] of integer;   |
  | var                                |
  |   p : ^ ar;                        |
  |                                    |
   ------------------------------------

'ar' is declared to be of type 'array of 10 integers';  'P' is a pointer variable that either
contains 'nil' or the address of an array of 10 inegers.

The NEW command is used to allocate space for the specified entity, and to return the
address so that the entity can be referenced.  Refer to the following executable code.

    p := nil;           { assign nil to p   (not always necessary) }

    p := new(ar);       { new as a function
                          allocate space for the array, then return the
                          address (of the first element) and place that
                          address into p }

    new(p);            {  new as a procedure
                          allocate space for the array, then return the
                          address (of the first element) and place that
                          address into p }

    p^[1] := 0;         { a reference to the first element of the array
                          pointed to by p.  To allocate the space, you could
                          have used either of the two forms (of the new
                          command) shown above. }


The NEW command of Turbo Pascal 5.5
-----------------------------------

Turbo Pascal 5.5 introduced 2 more forms of the New command, specifically to
accomodate objects.  The procedure form requires the 'pointer variable name' and the
name of the constructor as parameters.  The functional form requires the 'pointer variable
type' and the name of the constructor as parameters.

The following table shows the different forms of the 'New' command.

  ----------------------------------------------------------------------------
 |               |                       |                                    |
 |               |  procedures           |  functions                         |
 |--------------- ----------------------- ------------------------------------|
 |               |                       |                                    |
 | for a regular |  New(p1);             |  p1 := New(IntPtr);                |
 | variable      |                       |                                    |
 |--------------- ----------------------- ------------------------------------|
 |               |                       |                                    |
 | for an object |  New(p2,Init(49,64)); |  p2 := New(MyObjPtr,Init(81,100)); |
 |               |                       |                                    |
  ----------------------------------------------------------------------------

The general form of the 'New' function, as it appears in an assignment statement, is ...
pointer_variable := New(object_pointer_type,object_constructor(parameters));

... as in ...

   -----------------------------------
  | p2 := New(MyObjPtr,Init(81,100)); |
   -----------------------------------

... where MyObjPtr is declared to be a pointer variable type to an object, and Init is the name of the
constructor for the object.

The above statement allocates space for the object, runs the constructor (Init) with the
parameters given (81 and 100), then returns the address of the object and places that
address into the pointer variable p2 (given: var p2 : MyObjPtr).

Using MyObj from the previous program, one can use pointer variables to accomplish the
same tasks.  The new (or changed) statements have been flagged with the {} delimiter.

    program example (input,output);               { MYOBJ2.PAS }
    type
      MyObjPtr = ^ MyObj;                         create the pointer type  {}
      MyObj = object                              create the object type
        x,y : Integer;
        constructor Init(i,j : integer);
        procedure   Display_Values;
        destructor  Done;
      end;

    constructor MyObj.Init;                       declare the code associated
        begin                                     with each method
          x := i;
          y := j
        end;
      procedure MyObj.Display_Values;
        begin
          writeln('x = ',x,'  y = ',y);
        end;
      destructor MyObj.Done;
        begin
        end;

    var                                           declare the pointer variable
      My_Object : MyObjPtr;                       {}
    begin
      My_Object := New(MyObjPtr,Init(81,100));    {}  create the instance
      with My_Object^ do                          {}  reference the object
        begin
          Init(5,4);
          Display_Values;
          Done;
        end;
    end.


Virtual Methods and Overloading
-------------------------------

Turbo Pascal 5.5 allows a method to be declared 'virtual'.  What this means is that the
method can be overloaded and a reference to a method with the same name MIGHT
NOT BE a reference to THAT method.  It may be a reference to some other method with
the same name.  This philosophy does not follow conventional programming paradigms,
therefore an explanation is in order.  (For reference, the lines have been given numbers.)
Refer to the following program, which contains 1 virtual method that has been
overloaded.
   ------------------------------------------------------------------
  |  1  program sample0 (input,output);                              |
  |  2  uses crt;                                                    |
  |  3    type                                                       |
  |  4      Sample_One = object                                      |
  |  5        Sample_One_Item   : integer;                           |
  |  6        constructor Initialize(value : integer);               |
  |  7        procedure   Print_Value;  virtual;                     |
  |  8        destructor  Clean_Up;                                  |
  |  9      end;                                                     |
  | 10                                                               |
  | 11      constructor Sample_One.Initialize (value : integer);     |
  | 12        begin                                                  |
  | 13           with self do                                        |
  | 14            begin                                              |
  | 15              Sample_One_Item := value;                        |
  | 16              TextBackGround(Red);                             |
  | 17              Writeln('Sample_One.Initialize');                |
  | 18            end                                                |
  | 19        end;                                                   |
  | 20                                                               |
  | 21      procedure Sample_One.Print_Value;                        |
  | 22        begin                                                  |
  | 23          TextBackGround(Red);                                 |
  | 24          Writeln('Sample One Print Value  ',Sample_One_Item); |
  | 25        end;                                                   |
  | 26                                                               |
  | 27      destructor Sample_One.Clean_Up;                          |
  | 28        begin                                                  |
  | 29          TextBackGround(Red);                                 |
  | 30          Write('Sample One Clean Up - Calling ');             |
  | 31          Print_Value;                                         |
  | 32        end;                                                   |
  | 33                                                               |
  | 34    type                                                       |
  | 35      Sample_Two = object(Sample_One)                          |
  | 36        Sample_Two_Item   : integer;                           |
  | 37        constructor Initialize(value : integer);               |
  | 38        procedure   Print_Value;  virtual;                     |
  | 39      end;                                                     |
  | 40                                                               |
  | 41                                                               |
  | 42      constructor Sample_Two.Initialize (value : integer);     |
  | 43        begin                                                  |
  | 44           with self do                                        |
  | 45            begin                                              |
  | 46              Sample_Two_Item := value;                        |
  | 47              TextBackGround(White);                           |
  | 48              Writeln('Sample_Two.Initialize');                |
  | 49            end                                                |
  | 50        end;                                                   |
  | 51                                                               |
  | 52      procedure Sample_Two.Print_Value;                        |
  | 53        begin                                                  |
  | 54          TextBackGround(White);                               |
  | 55          Writeln('Sample Two Print Value ',Sample_Two_Item);  |
  | 56        end;                                                   |
  | 57                                                               |
  | 58  var                                                          |
  | 59    L1 : Sample_One;                                           |
  | 60    L2 : Sample_Two;                                           |
  | 61  begin                                                        |
  | 62    ClrScr;                                                    |
  | 63    TextColor(Yellow);                                         |
  | 64    L1.Initialize(10);                                         |
  | 65    L1.Print_Value;                                            |
  | 66    L1.Clean_Up;                                               |
  | 67    writeln;                                                   |
  | 68    readln;                                                    |
  | 69    L2.Initialize(20);                                         |
  | 70    L2.Print_Value;                                            |
  | 71    L2.Clean_Up;                                               |
  | 72    writeln;                                                   |
  | 73  end.                                                         |
   ------------------------------------------------------------------


The lines numbered 3 through 32 define an object called 'Sample_One'.  The object
contains 1 data item (Sample_One_Item), and the required constructor (Initialize) and the
required destructor (Clean_Up).  There is, also, 1 other procedure (Print_Value) that has
been included.  Notice the word 'virtual' at line 7.  Virtual is a keyword and means that
the corresponding procedure can (but does not HAVE to) be overloaded; this is to say,
there can be another procedure (called Print_Value) that may supercede this one when
the program starts running.

The lines numbered 34 through 56 define an object called 'Sample_Two'.  The object
(which is derived from Sample_One - see line 35) inherits all of the data fields and
methods from Sample_One EXCEPT for the ones that were overloaded (over-ridden).
It might help to think of the structure of Sample_Two as ...

         Sample_Two = object
     -->   Sample_One_Item   : integer;
           Sample_Two_Item   : integer;
           constructor Initialize(value : integer);
           procedure   Print_Value;  virtual;
     -->   destructor  Clean_Up;
         end;

with the items marked with an arrow  ('-->') inherited from Sample_One. (Initialize and
Print_Value were over-ridden.)

Lines 64 through 66, in the main program, reference L1 - an instance of Sample_One.
Lines 69 through 71 reference L2 - an instance of Sample_Two. The interesting part is
lines 70 & 71.  Line 70 is a call on Sample_Two.Print_Value which is an overloaded
version of Sample_One.Print_Value.  The code at lines 43 through 50 will execute.
On the other hand, line 71 is a call on Sample_Two.Print_Value which is an inherited
procedure that comes from Sample_One.  The code at lines 28 through 32 will execute.

L1.Cleanup (which is an instance of Sample_One.Cleanup) invokes
Sample_One.Print_Value (at line 31), as might be expected.  However, L2.Cleanup
(inherited from Sample_One) will invoke Sample_Two.Print_Value.  If we take another look
at the (presumed) structure of Sample_Two (below), the destructor Clean_Up (at line 6)
would reference the Print_Value defined at line 5.

1         Sample_Two = object
2     -->   Sample_One_Item   : integer;
3           Sample_Two_Item   : integer;
4           constructor Initialize(value : integer);
5           procedure   Print_Value;  virtual;
6     -->   destructor  Clean_Up;
7         end;

Take a deep breath.
UNITS

Turbo Pascal (from Version 4.0 on up) implements UNITS, which allow the programmer
to group together variables, procedures & functions and treat them as a single item.  A
Turbo Pascal Unit is an Abstract Data Type and defines 4 entities:

  1  a data structure
  2  the routines that access that data structure
  3  some initialization code
  4  the user interface


In the following example, the data structure is actually 2 items.  The first item is a stack,
st, of 30 integers.  The second item is a number, top, that is used to keep track of the top
item on the stack.  The declarations are shown below.

   -----------------------------------------
  |                                         |
  |   var                                   |
  |     st  : array [1..30] of integer;     |
  |     top : integer;                      |
  |                                         |
   -----------------------------------------

The routines that access the data structure are specified next.  For this definition of a
stack, we have chosen to implement two (2) routines that access the stack: push and
pop.

   -----------------------------------------
  |                                         |
  |   procedure push ( i : integer);        |
  |     begin                               |
  |       top := top + 1;                   |
  |       st[top] := i                      |
  |     end;                                |
  |                                         |
  |   function pop : integer;               |
  |     begin                               |
  |       top := top - 1;                   |
  |       pop := st[top+1]                  |
  |     end;                                |
  |                                         |
   -----------------------------------------

The initialization code consists of 1 line that will zero out the stack top pointer,
ensuring that the stack starts out empty.

   -----------------------------------------
  |                                         |
  |   begin                                 |
  |     top := 0;                           |
  |   end.                                  |
  |                                         |
   -----------------------------------------

Finally, we specify the interface.  The interface consists of 1 or more identifers (types,
variables, procedures or functions) that can be referenced by some other program (or
subprogram) OUTSIDE of the package. Also the parameter passing conventions are
specified.

   -----------------------------------------
  |                                         |
  |   interface                             |
  |     procedure push ( i : integer);      |
  |     function  pop : integer;            |
  |                                         |
   -----------------------------------------

This completes the definition of each individual part.



The overall structure of a UNIT in Turbo Pascal is as follows:

   unit name

   interface part
     1 or more procedures and/or functions (with parameters specified)

   implementation part
     variable declarations
     procedures
     functions
     initialization code

'Unit', 'interface' and 'implementation' are keywords.  Using this structure, the entire
package is shown below.


    ---------------------------------------
   |                                       |
   |  unit stack;                          |
   |                                       |
   |  interface                            |
   |    procedure push ( i : integer);     |  ««« 2 gloabally known entities
   |    function  pop : integer;           |      (push and pop)
   |                                       |
   |  implementation                       |  ««« everything, after the keyword
   |                                       |      'implementation', is local
   |    var                                |
   |      st  : array [1..30] of integer;  |  ««« the data structure (DS)
   |      top : integer;                   |
   |                                       |
   |  procedure push(i:integer);           |  ««« what follows is the code
   |  begin                                |      that accesses the data
   |    top := top + 1;                    |      structure
   |    st[top] := i                       |
   |  end;                                 |
   |                                       |
   |  function pop : integer;              |
   |  begin                                |
   |    top := top - 1;                    |
   |    pop := st[top+1]                   |
   |  end;                                 |
   |                                       |
   |  begin                                |
   |    top := 0;                          |  ««« the initialization code
   |  end.                                 |
   |                                       |
    ---------------------------------------

This package is then compiled like any other module.  Compiling the above routine will
create a 'tpu' file.  If the name of the file that contained the above code was called
'stack.pas' then the name of the unit would be called 'stack.tpu'.  It is recommended (but
not required) that the filename prefix and the unit name be the same.

Now, a package is not a working program.  Do not try to run it.  It is merely a tool that
can be referenced BY a working program.  Availability to the stack is accomplished by
the USES statement.  In general, the syntax of the USES statement is:

      uses {$U path\filename} package_name;

... and for the stack package, the syntax would be:

      uses stack;

The keyword 'uses' may be followed by the compiler directive  $U.  If the main program
and the unit are contained withing the same directory OR the path name of the unit is
specified in the TPU path, then the $U is not needed.  (If the filename and the unit name
are different, then the $U compiler directive is necessary.)

The assumption is, of course, that there is a file called stack.tpu which was created by
compiling the unit stack.pas.  The types, variables, procedures and functions listed in the
interface are then available to any program that contains the statement:

  uses stack.

The following program 1) references the stack and 2) uses the different features of the
stack.


   -----------------------------------------
  |                                         |
  |  program stack_test;                    |
  |  uses stack;                            |
  |  var                                    |
  |    i : integer;                         |
  |  begin                                  |
  |    push(5);                             |
  |    push(7);                             |
  |    i := pop;                            |
  |    writeln(i);                          |
  |    i := pop;                            |
  |    writeln(i);                          |
  |  end.                                   |
  |                                         |
   -----------------------------------------

The uses command causes the initialization code (in the unit) to be executed and also
allows the user access to the identifiers listed in the interface section.  Then the rest of
the program continues normally.

Once the main program is keyed in, it is compiled and run.



Storing Object in Units
-----------------------

Normally objects are stored in units so that references, by other programs, is fast and
easy.  In the following example, we have taken the program above (titled sample0) and
divided it into 3 parts - 2 units and 1 main program. The first unit is called sample1_.

Now the object itself is placed in the interface part.  The code for the methods is in the
implementation part.  The typical user is only interested in the names of the methods and
the parameters.  The details of implementation have been 'hidden', that is to say that the
main program can access ONLY those entities in the interface part.  Any types, variables,
procedures or functions that appear in the implementation part but DO NOT appear in
the interface part, are considered local and cannot be accessed outside of this unit.

   -----------------------------------------------------------------
  | unit                                                            |
  |   sample1_;                                                     |
  |                                                                 |
  | interface                                                       |
  | uses crt;                                                       |
  |                                                                 |
  |   type                                                          |
  |     Sample_One_Ptr = ^ Sample_One;                              |
  |     Sample_One = object                                         |
  |       Sample_One_Item   : integer;                              |
  |       constructor Initialize(value : integer);                  |
  |       procedure   Print_Value;  virtual;                        |
  |       destructor  Clean_Up;                                     |
  |     end;                                                        |
  |                                                                 |
  | implementation                                                  |
  |     constructor Sample_One.Initialize (value : integer);        |
  |       begin                                                     |
  |          with self do                                           |
  |           begin                                                 |
  |             Sample_One_Item := value;                           |
  |             TextBackGround(Red);                                |
  |             Writeln('Sample_One.Initialize');                   |
  |           end                                                   |
  |       end;                                                      |
  |                                                                 |
  |     procedure Sample_One.Print_Value;                           |
  |       begin                                                     |
  |         TextBackGround(Red);                                    |
  |         Writeln('Sample One Print Value  ',Sample_One_Item);    |
  |       end;                                                      |
  |                                                                 |
  |     destructor Sample_One.Clean_Up;                             |
  |       begin                                                     |
  |         TextBackGround(Red);                                    |
  |         Write('Sample One Clean Up - Calling ');                |
  |         Print_Value;                                            |
  |       end;                                                      |
  | begin                                                           |
  | end.                                                            |
   -----------------------------------------------------------------

This unit will be stored in a file called sample1_.pas and compiled, creating sample1_.tpu.
In this particular example, there is NO initialization code for the package.



The next unit is sample2_.  The difference between this unit and the previous one is that
Sample_Two is an object DERIVED from Sample_One and all the methods from
Sample_One (that have NOT been overloaded) are accessible and (could be considered)
part of Sample_Two.  Specifically, Sample_Two contains a destructor (called) Clean_Up
that it inherited from Sample_One.  (It also contains a record item called
Sample_One_Item, that it does not reference.)

   ----------------------------------------------------------------
  | unit                                                           |
  |   sample2_;                                                    |
  |                                                                |
  | interface                                                      |
  |                                                                |
  | uses                                                           |
  |   crt, sample1_;                                               |
  |                                                                |
  |   type                                                         |
  |     Sample_Two_Ptr = ^ Sample_Two;                             |
  |     Sample_Two = object(Sample_One)                            |
  |       Sample_Two_Item   : integer;                             |
  |       constructor Initialize(value : integer);                 |
  |       procedure   Print_Value;  virtual;                       |
  |     end;                                                       |
  |                                                                |
  | implementation                                                 |
  |                                                                |
  |     constructor Sample_Two.Initialize (value : integer);       |
  |       begin                                                    |
  |          with self do                                          |
  |           begin                                                |
  |             Sample_Two_Item := value;                          |
  |             TextBackGround(White);                             |
  |             Writeln('Sample_Two.Initialize');                  |
  |           end                                                  |
  |       end;                                                     |
  |                                                                |
  |     procedure Sample_Two.Print_Value;                          |
  |       begin                                                    |
  |         TextBackGround(White);                                 |
  |         Writeln('Sample Two Print Value ',Sample_Two_Item);    |
  |       end;                                                     |
  |                                                                |
  | begin                                                          |
  | end.                                                           |
   ----------------------------------------------------------------

Note again: there is no procedure called Sample_Two.Clean_Up, but since it was
inherited from Sample_One, it exists, nevertheless.  This unit is then stored in a file
(arbitraily) called sample2_.pas and compiled, creating sample2_.tpu.

The main program is all that is left.  Now, to reference the entities defined in sample1_
and sample2_, the user keyes in the statement:

uses
  sample1_, sample2_;



The entire main program follows.

   ----------------------------
  | program sample;            |
  | uses                       |
  |   crt, sample1_, sample2_; |   make sample1_ and sample2_ accessible
  | var                        |
  |   L1 : Sample_One;         |   create an instance of the object sample_one
  |   L2 : Sample_Two;         |   create an instance of the object sample_two
  | begin                      |
  |   ClrScr;                  |
  |   TextColor(Yellow);       |
  |                            |
  |   L1.Initialize(10);       |   reference to Sample.One
  |   L1.Print_Value;          |     "
  |   L1.Clean_Up;             |     "
  |   writeln;                 |
  |                            |
  |   L2.Initialize(20);       |   reference to Sample.Two
  |   L2.Print_Value;          |     "
  |   L2.Clean_Up;             |     "
  |   writeln;                 |
  | end.                       |
   ----------------------------


Singly Linked Lists using OOP
-----------------------------

The following code implements a singly linked list that can hold nodes that contain
Integers OR Strings.  This example (in it's original form) was distributed with Turbo Pascal
Ver 5.5 and was probably written by employees of Borland International.  The original
code was then divided into several different units for pedagogical purposes.  The first
TYPE described (and the lowest in the hierarchy) is the BASE_ type.  Base is an abstract
object type, and serves only as an ancestor for other object types.  Objects of type Base
are never to be instantiated.  There is only 1 method in Base (called Done) therefore
object types derived from Base are guaranteed to have a destructor called Done.  Unless
a user overrides it, the Done destructor in Base does nothing except to dispose of the
instance when called (a feature of all destructors).


unit base_;
{ This unit defines the BASE type. }
{$S-}

interface
  type
    BasePtr = ^Base;
    Base = object
      destructor Done; virtual;
    end;

implementation
  destructor Base.Done;
  begin
  end;

end.


The next part describes the 'Node' type.  The node type is a descendent of base.  Node
is an abstract Linked List node type that serves as the common ancestor of all items that
are kept on linked lists using the List type.  The Next field points to the next node on the
list.  In this example the list is circular, therefore the Next field of the last node on
the list points to the first node.  The Prev method returns a pointer to the preceding
node.  The Prev method of the first node returns a pointer to the last node on the list.
Note that Node is a descent of Base (re: Node = object(Base)) and, therefore, already
contains a destructor named Done.  The interesting method is Display.  Display is a
virtual method - which means it can be overridden. In point of fact, it better be
overridden, because if some code invokes Node.Display an error message will be printed
and execution will abort - not a desirable occurrance.


unit
  node_;
{ This unit defines the basic node. }

{$S-}

interface
  uses
    base_;

type
  NodePtr = ^Node;
  Node = object(Base)
    Next : NodePtr;                        { pointer to next node }
    procedure Display; virtual;            { to be over-ridden }
    function Prev : NodePtr;               { pointer to the previous node }
  end;

implementation

procedure Node.Display;                    { if invoked then print an error }
begin
  RunError(211);
end; {Node.Display}

function Node.Prev : NodePtr;
{ Returns a pointer to the node BEFORE N ( using a Linear Search).  If N
  points to the first node in the list, then Prev returns the last node in
  the list.  }
var
  P: NodePtr;
begin
  P := @Self;
  while P^.Next <> @Self do P := P^.Next;
  Prev := P;
end; {Node.Prev}

end.



The next part describes the 'IntNode' type.  The IntNode type is a descendent of node.
IntNode is an instance of the Node type and instances of this type are stored on the
Linked List.  An IntNode contains an Integer ('Value' which is the data portion of the
node).  IntNode also contains a contructor called 'Init' which is used to create space for
the node and to initialize the value in 'Value'.  The interesting part of IntNode is procedure
Display, which overloads Node.Display.  If a reference to IntNode refers to Display it will
be IntNode.Display and NOT Node.Display.  This way some other routine (re: List.Display)
can refer to p^.Display (not knowing what p^ points to) and this reference will invoke
IntNode^.Display (if and when p points to an instance of an IntNode).

unit
  intnode_;

interface
uses
  crt, Node_;

type
  IntNodePtr = ^IntNode;
  IntNode = object(Node)
    Value     : Longint;
    constructor Init(V: Longint);
    procedure   Display; virtual;
  end;


implementation

constructor IntNode.Init(V: Longint);
begin
  Value := V;
end;

procedure IntNode.Display;
begin
  WriteLn(Value);
end;

begin
end.



The next part describes the 'StrNode' type.  The StrNode type is a descendent of node.
StrNode is an instance of the Node type and instances of this type are stored on the
Linked List.  A StrNode contains a pointer to a String ('Value' which contains the address
of the data portion of the node). StrNode also contains a contructor called 'Init' which is
used to 1) create space for the node, 2) allocate space for the string and 3) to assign the
value in 'Value^'.  The interesting part of StrNode is procedure Display, which overloads
Node.Display.  If a reference to StrNode refers to Display it will be StrNode.Display and
NOT Node.Display.  This way some other routine (re: List.Display) can refer to p^.Display
(not knowing what p^ points to) and this reference will invoke StrNode^.Display (if and
when p points to an instance of a StrNode).

StrNode is similar to IntNode except that it deals with strings and not Integers.  All of the
code is specific to strings.


unit
  StrNode_;

interface
uses
  crt, Node_;

type
  StringPtr = ^String;

  StrNodePtr = ^StrNode;
  StrNode = object(Node)
    Value     : StringPtr;
    constructor Init(V: String);
    destructor  Done; virtual;
    procedure   Display; virtual;
  end;

implementation

constructor StrNode.Init(V: String);
begin
  GetMem(Value, Length(V) + 1);
  Value^ := V;
end;

destructor StrNode.Done;
begin
  FreeMem(Value, Length(Value^) + 1);
end;

procedure StrNode.Display;
begin
  WriteLn('String: ', Value^);
end;

begin
end.



The next part describes the 'List' type.  The List type is not a descendent of anything. List
is an object that contains the fields and methods necessary to maintain a linked list.  Last
is a pointer variable that points to the last item in the list.  If Last contains nil then the list
is empty. Last^.Next, points to the first node on the list, maing the List circular.

The List type implements the basic algorithms of a circular linked list. The objects kept
on a List are derived from the abstract Node type.  The List type does not inherit and has
no virtual methods. For that reason, no constructors or destructors are needed in the List
object.

List contains several methods that are associated with linked lists. Append(N : NodePtr)
appends a node on the end of the list. Clear clears the list (sets Last = nil.  Delete
traversses the list deleting all of the nodes (using the Done destructors of each node).
The boolean function Empty returns the value TRUE is the list is empty.  The function First
returns a pointer to the first node on the list.  The procedure Insert (N : NodePtr) inserts
the node, pointed to by N onto the front of the list.  The function Next (N : NodePtr)
returns a pointer to the node that follows N in the list. The function Prev (N : NodePtr)
returns a pointer to the node that precedes N in the list.  The procedure Remove (N :
NodePtr) removes the node pointed to by N from the list.

The interesting part of List is procedure Display, which traversses the List and displays
the Value fields of the different nodes. If the current value of p points to an instance of
an IntNode, then an integer will be printed.  If the current value of p points to an instance
of an StrNode, then a string will be printed.  List.Display does not know (nor care) what
the current value of p points to, but whatever it points to the appropriate Display
procedure will be invoked.  Borland call this Object Oriented Programming.


unit List_;
{$S-}

interface
uses
  node_;
type
  ListPtr = ^List;
  List = object         { defines a circular, singly linked list }
    Last : NodePtr;     { last is the tail pointer }
    procedure Append(N : NodePtr);
    procedure Clear;
    procedure Delete;  { the whole list }
    procedure Display;
    function  Empty  : Boolean;
    function  First  : NodePtr;
    procedure Insert (N : NodePtr);
    function  Next   (N : NodePtr): NodePtr;
    function  Prev   (N : NodePtr): NodePtr;
    procedure Remove (N : NodePtr);
  end;

implementation


List.Append will Append  N->Node  onto the END of a list.  The new node becomes the
last node in the list.

procedure List.Append(N: NodePtr);
  begin
    Insert(N);
    Last := N;
  end;


List.Clear will Clear the list by setting the 'Last' field to NIL.  Any nodes on the list
will NOT be disposed of properly.  This procedure is usually invoked upon startup (ensuring
an empty list).

procedure List.Clear;
  begin
    Last := nil;
  end;


List.Delete will delete ALL items from the list and de-allocate the space properly.  It
disposes of all nodes on the list using their respective Done destructors.

procedure List.Delete;
  var
    P: NodePtr;
  begin
    while not Empty do
      begin
        P := First;
        Remove(P {from the list});
        Dispose({of} P, {using the method} Done);
      end;
  end;


List.Display will traverse through the list displaying each value in the respective nodes.
P^.Display, in the code below, does NOT refer to List.Display, so this code is NOT
recursive.  P^.Display refers, instead, to the Display routine that overloads Node.Display.
P could, possibly, point to an IntNode or it could point to a StrNode.  The Display method
corresponding to the type of the item P points to will be invoked.  Different iterations
through the loop cause different types of nodes to be referenced (by P) causing different
versions of Display to be invoked.  If P is pointing to an Int_Node, then Int_Node.Display
will be referenced. If P is pointing to a Str_Node, then Str_Node.Display will be
referenced.  The interesting part of this is that this routine can be compiled (into a .tpu
file), and distributed, long before any of the various Node routines are even written.  A
user, with just List.Tpu available, can create a new node type, say, Fl_Node_ (for floating
point numbers), write the necessary routines (similar to the routines in IntNode and
StrNode), include the unit name in the main program and THIS routine (List.Display) can
access nodes of the new type.

Procedure List.Display;
var
  P: NodePtr;
begin
  P := First;
  while P <> nil do
    begin
      P^.Display;
      P := Next(P);
    end;
end;


List.Empty is a function that returns true if the list is empty.

function List.Empty: Boolean;
  begin
    Empty := Last = nil;
  end;


List.First returns a pointer to the first node on the list, or NIL if the list is empty. The first
node on the list is accessed (indirectly) using the 'Last' field of the List object.

function List.First: NodePtr;
  begin
    if Last = nil then First := nil else First := Last^.Next;
  end;


List.Insert will insert an item (pointed to by N) onto the FRONT of the Circular Linked List.
It does this by inserting AFTER the last item in the list.

procedure List.Insert(N: NodePtr);
  begin
    if Last = nil then Last := N else N^.Next := Last^.Next;
    Last^.Next := N;
  end;



List.Next returns a pointer to the node following N, or NIL if N is the last node.  This 'NIL
feature' allows the user to treat the circular linked as a regular linked list, if necessary.

function List.Next(N: NodePtr): NodePtr;
  begin
    if N = Last then Next := nil else Next := N^.Next;
  end;


List.Prev returns the location of the node BEFORE node N, or NIL of N is the first node.
(If this node is part of a Singly Linked List, then N^.Prev will search all the way around
the circular list looking for the predecessor. If this node is part of a Doubly Linked List,
then N^.Prev will refer to an overloaded version and return the contents of the Previous
Link Pointer.)

function List.Prev ( N : NodePtr ) : NodePtr;
  begin
    if N = First then Prev := nil
    else Prev := N^.Prev; { N^.Prev refers to a function within Node }
  end;


Removes a node from a List. The node itself is not disposed.

procedure List.Remove(N: NodePtr);
  var
    P: NodePtr;
  begin
    if Last <> nil then
      begin
      { find P such that P^.Next points to node N }
        P := Last;
        while (P^.Next <> N) and (P^.Next <> Last) do P := P^.Next;
      { remove node N from the list }
        if P^.Next = N then
          begin
            P^.Next := N^.Next;
            if Last = N then if P = N then Last := nil else Last := P;
          end;
      end;
  end;
end.



The main program, called CREATE, ties all of the above components together into a
single entity.  The statement...

uses
  Crt, IntNode_, StrNode_, List_;

... allows the user to access the types and methods defined in the preceding units.

program Create;
{ ■ Creates an Object Oriented Linked List.
  ■ Copyright (c) 1989 by Borland International,Inc.}
uses
  Crt, IntNode_, StrNode_, List_;
var
  L : List;
begin
  ClrScr;
  WriteLn('Creating list.');
  L.Clear;

The following statement ...

  L.Append(New(IntNodePtr, Init(1)));

... does several things.  1) It creates a new instance of whatever IntNodePtr points to -
that is, an Integer Node.  2) It then calls the Init constructor for IntNode and passes the
value 1 (which is then placed into the field 'Value').  3) It returns the address of the node
and then uses that address to append the node on the end of the list referred to by L.

The first part of the program appends 7 integers and 4 strings into the Singly Linked List
referred to by L.

  L.Append(New(IntNodePtr, Init(10)));
  L.Append(New(IntNodePtr, Init(100)));
  L.Append(New(IntNodePtr, Init(1000)));
  L.Append(New(IntNodePtr, Init(10000)));
  L.Append(New(StrNodePtr, Init('Hello world')));
  L.Append(New(StrNodePtr, Init('Turbo Pascal version 5.5')));
  L.Append(New(StrNodePtr, Init('Bill McDaniel  Edmond, Ok   73034')));

The second part of the program displays a header then invokes L.Display - which refers
to List.Display.  List.Display traversses the list displaying the contents of the Value field
in each and every node.  Using the properties of objects, when List.Display invokes the
node Display routine (re: P^.Display;), the Display routine from the node corresponding
to the type of the item in the node will be used.  If P points to an IntNode, then
IntNode.Display will be used.  If P points to a StrNode, then StrNode.Display will be used.
The routine selected depends upon the type of the entity being referenced.

  ClrScr;
  GotoXY(1,1);
  WriteLn('List to be saved:');
  L.Display;

The third part of the program deletes the list.

  WriteLn('Deleting list.');
  L.Delete;
end.



There was PeopleGen.Pas which generated a People Data Base that had fields
associated with people, their addresses and their phone numbers.

  ----------------------------  People Data Base  --------------------------
 |                                                                          |
 | Firstname:  ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  Lastname: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   |
 |                                                                          |
 | Address:    ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                                   |
 |                                                                          |
 | City: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  State: ▒▒ Zip:▒▒▒▒▒ Phone: ▒▒▒▒▒▒▒▒         |
 |                                                                          |
 |                                                                          |
 |                                                                          |
  --------------------------------------------------------------------------

There was CamGen.Pas which generated a CAM Data Base that had fields
associated with participants at the Conference on Applied Mathematics held at
the University of Central Oklahoma during the spring semester of 1993.

   ---------  Conference on Applied Mathematics  Data Base  ------------
 |                                                                      |
 |  Firstname˙ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   Lastname˙ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒   |
 |                                                                      |
 |  Institution˙ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                           |
 |                                                                      |
 |  Address˙ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                           |
 |  City   ˙ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  State˙ ▒▒ Zip˙ ▒▒▒▒▒                   |
 |  Phone Number˙ ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                                     |
 |                                                                      |
 |  Registration Type { b r s }˙  ▒        Advance or Onsite  { a o }˙▒ |
 |                                                                      |
 |  Additional Banquet Tickets˙ ▒▒          Copies of Proceedings˙ ▒▒   |
 |                                                                      |
 |  Amount Paid˙ ▒▒▒                        Date Paid˙ ▒▒▒▒▒▒▒▒         |
 |                                                                      |
 |  Receipt Printed˙  ▒                                                 |
 |                                                                      |
 |                                                                      |
  ----------------------------------------------------------------------

And, there was SFGen.Pas which generated a Science Fair Data Base that
contained fields associated with participants at the UCO Regional Science
Fair held at University of Central Oklahoma during the spring semester of
1993.

  ------------------------  Science Fair Data Base  ------------------------
 |                                                                          |
 | First Name: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  MI:  ▒                      |
 |       Last: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒                              |
 |        SSN: ▒▒▒▒▒▒▒▒▒▒▒                     Age: ▒▒   Grade: ▒▒          |
 |                                                                          |
 | Address: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  City: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  Zip ▒▒▒▒▒  |
 |                                                                          |
 | Phone Number:˙˙˙˙ ▒▒▒▒▒▒▒▒                                               |
 | Title of Project: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  |
 | Category: ▒▒▒▒       Display Number:  ▒▒▒                                |
 | Sponsoring Teacher Name: ▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒▒  |
 | Experiment Involves: ▒▒▒▒▒▒▒▒▒▒▒▒▒   Award (1st, 2nd or 3rd place): ▒    |
 | Special Award  1: ▒▒▒▒▒ Special Award  2:  ▒▒▒▒▒                         |
 | Special Award  3: ▒▒▒▒▒ Special Award  4:  ▒▒▒▒▒                         |
 | Special Award  5: ▒▒▒▒▒ Special Award  6:  ▒▒▒▒▒                         |
 | Special Award  7: ▒▒▒▒▒ Special Award  8:  ▒▒▒▒▒                         |
 | Special Award  9: ▒▒▒▒▒ Special Award 10:  ▒▒▒▒▒                         |
 |                                                                          |
  --------------------------------------------------------------------------

The reader will note that the fields are all different and the appearances
of the update screens are all different.  And yet, there is just 1 (one)
UpDate program that can be used to update ALL of the different Data Bases.

To have this flexibility, the information about the fields was stored in
the same file as the data.

I was definitely intriqued.  Pascal is not a language known to support variant
records.  I was interested in knowing the manner in which the one UPDATE
program could access different files with different types of fields in each
file.  Once the method was determined, I wanted to create a special purpose
data base specifically for enrollment.  This paper describes the method used
(by the programmers at Borland, Int'l) to implement variant records in
Turbo Pascal 5.5, and also includes the changes I made to the code to
implement the desired new features.


Overview of EnrolGen.Pas and EnrolUpd.Pas
-----------------------------------------

Creation of the Data Base is accomplished by a program called EnrolGen.Pas.
Updating the Data Base is done by EnrolUpd.Pas.  The following table shows
the steps involved to create and update an OOP Data Base.  The Roman Numerals
to the left are the paragraph numbers where each item is described in this
report.


  EnrolGen.Pas

  I    ˙ Create the database (using EnrolGen.Pas)

  EnrolUpd.Pas

  II   ˙ Read in the existing Form from a file
  III  ˙ Add, Update and Delete the Forms
  IV   ˙ Write the existing Form to the file

  V    ˙ The authors changes to the design

I Create the database using EnrolGen.Pas
  ======================================

  I-1  ˙ Declare the Form
  I-2  ˙ Create the Form
  I-3  ˙ Add all of the various fields to the Form
  I-4  ˙ Write the Existing Form to the file
  I-5  ˙ Declare the CardForm
  I-6  ˙ Create the Card Form
  I-7  ˙ Write (an empty) Card Form to the Disk


I-1  Declare the Form
     ================
  var
    F : Form;

  F is an instance of Form (as shown below).  A Form is a Window that contains
1 or more fields.  The information about the form appears first (X1, Y1, X2,
Y2, Size, Shadow and Message).  The last field is Fields which is an object
of type List.

Form has the following structure.

  FormPtr = ^Form;
  Form = object(Base)
    X1, Y1,                 { window coordinates }
    X2, Y2,
    Size : Integer;         { size of the form }
    Shadow : boolean;
    Message :    String;    { message that appears at the top of the form }
    Fields  :    List;      { a sll of items in the form }
    constructor  Init(PX1, PY1, PX2, PY2: Integer; Msg : String; PShadow : Boolean);
    constructor  Load(var S: Stream);   { load the form from the disk }
    destructor   Done; virtual;         { delete all the fields in the form }
    function     Edit : Char;           { edit the form (a field at a time) }
    procedure    Show(Erase: Boolean);  { display the form (a field at a time) }
    procedure    Add(P: FieldPtr);      { add a new field to the form - increase Size}
    procedure    Clear;                 { clear the form ( a field at a time) }
    procedure    Get(var FormBuf);      { move from 1 buffer to each field }
    procedure    Put(var FormBuf);      { move each field into 1 big buffer }
    procedure    Store(var S: Stream);  { write form to disk - then write fields }
  end;


  The Fields field in Form is of type List.  List has the following structure.
List contains a Last pointer plus all of the routines that refer to linked
lists.  More information on the List type can be found in McDa92.

  ListPtr = ^List;
  List = object         { defines a circular, singly linked list }
    Last : NodePtr;     { last is the tail pointer }
    procedure Append(N : NodePtr);
    procedure Clear;
    procedure Delete;  { the whole list }
    function  Empty         : Boolean;
    function  First         : NodePtr;
    procedure Insert (    N : NodePtr);
    procedure Load   (var S : Stream);
    function  Next   (    N : NodePtr): NodePtr;
    function  Prev   (    N : NodePtr): NodePtr;
    procedure Remove (    N : NodePtr);
    procedure Store  (var S : Stream);
  end;


I-2  Create the Form
     ===============

  In the main DataBase Create program (such as EnrolGen) the Form is created
  with the following line.

    F.Init(2, 3,  0, -1,' Enrollment Data Base ',False);

  The code for Form.Init follows.  The X,Y coordinates are the top-left and
bottom right corners of the window where the form appears.  The Size field
starts out with the value zero, but is changed as new items are added to the
form.  The Message appears at the top of the Form window.  The Shadow field
causes a shadow to be drawn (if true).  The Fields field is a linked list,
which is cleared by setting the Last pointer to nil.

constructor Form.Init(PX1, PY1, PX2, PY2: Integer; Msg : string; PShadow : Boolean);
  begin
    X1 := PX1;
    Y1 := PY1;
    X2 := PX2;
    Y2 := PY2;
    Size := 0;    { starts out at zero }
    Message := Msg;
    Shadow := PShadow;
    Fields.Clear;
  end;


procedure List.Clear;
{ Clears the list by setting the 'Last' field to NIL. Any nodes on the list
  will NOT be disposed of properly. }
  begin
    Last := nil;
  end;


I-3  Add all of the various fields to the Form
     =========================================

Adding the fields to the Data Base is relatively straightforward.  The
programmer would have to specify the type of the item pointed to (as in,
Dept_CoursePtr), the Position (X,Y coordinates) of the item to be entered
or updated (2, 2), the Title of the item to be updated (Dept Course), and
the size of the field (10).  A detailed explanation of the operation of
F.Add(...) will appear later.


    F.Add(New(Dept_CoursePtr,     Init(  2,   2,  'Dept Course    ', 10)));
    F.Add(New(Evening_ClassPtr,   Init( 32,   2,  'Evening Class  ',  1)));
    F.Add(New(SectionPtr,         Init( 54,   2,  'Section        ',  4)));
    F.Add(New(Start_Time_1Ptr,    Init(  2,   4,  'Start Time 1   ',  8)));
    F.Add(New(Stop_Time_1Ptr,     Init( 32,   4,  'Stop Time 1    ',  8)));
    F.Add(New(Start_Time_2Ptr,    Init(  2,   5,  'Start Time 2   ',  8)));
    F.Add(New(Stop_Time_2Ptr,     Init( 32,   5,  'Stop Time 2    ',  8)));
->  F.Add(New(Days_MeetPtr,       Init(  2,   7,  'Days Meet      ',  5)));
    F.Add(New(BuildingPtr,        Init( 32,   7,  'Building       ',  5)));
    F.Add(New(RoomPtr,            Init( 54,   7,  'Room           ',  5)));
    F.Add(New(InstructorPtr,      Init(  2,   9,  'Instructor     ', 30)));
    F.Add(New(CommentsPtr,        Init(  2,  11,  'Comments       ', 58)));
    F.Add(New(Room_SizePtr,       Init(  2,  13,  'Room Size      ',  3)));
    F.Add(New(Max_Class_SizePtr,  Init( 32,  13,  'Max Class Size ',  3)));
  end;

As an example, and to show the layout of the fields, the Days_Meet object has
been expanded below.  The other fields are similar in nature.  The Days_Meet
object, does not contain any new fields, just three methods.  The method
Init creates the object, Clean is a virtual method that 'cleans' up the
field just after it is entered, and Help displays a Help screen for this
field if the F1 key is pressed.  Now, Clean and Help are virtual methods, and
which are declared at various levels in the structure.  When Clean is invoked,
the code defined in the highest level is used.

  Layout of the Days_Meet object
  ------------------------------

  Days_MeetPtr = ^Days_Meet;
  Days_Meet =
    object(Enrollment_Field)
      constructor  Init(PX, PY: Integer; PTitle: FString; PLen: Integer);
      procedure    Clean; virtual;    { the authors }
      procedure    Help;  virtual;    { the authors }
    end;

The Days_Meet object is an object of type Enrollment_Field, and therefore it
inherits all of the fields and methods from Enrollment_Field.
Enrollment_Field has three methods, Init (to initialize the object), GetStr
(to return the value of the item stored), and PutStr (to save the value of
the item stored).

  Enrollment_Field_Ptr = ^Enrollment_Field;
  Enrollment_Field =
    object(FText)
      constructor  Init(PX, PY: Integer; PTitle: FString; PLen: Integer);
      procedure    GetStr(var S: FString); virtual;
      function     PutStr(var S: FString): Boolean; virtual;
    end;

Enrollment_Field is an object of type Field and inherits all of the fields
and methods of Field.  The FText object contains a Len field that contains
the length of the field being referenced.  It also contains five methods:
Init (to initialize the object), Edit (to key in or change the field),
GetStr (to retrieve the value), PutStr (to store the value) and Show (to
display the value on the screen).

  FTextPtr = ^FText;
  FText = object(Field)
    Len: Integer;            { Maximum length of the field }
    constructor  Init(PX, PY, PSize: Integer; PTitle: FString; PLen: Integer);
    function     Edit: Char; virtual;
    procedure    GetStr(var S: FString); virtual;
    function     PutStr(var S: FString): Boolean; virtual;
    procedure    Show; virtual;
  end;

FTest is an object of type Field, which contains information about a
generic item.  Field contains: X, Y (coordinates of the field when it appears
on the screen), Size (the size of the field in bytes), Title (the message
that appears before the data), Value (a pointer variable that points to the
actual data), and Extra (the starting location of other fields).  The
presence of Extra requires some explanation.  When the type FText is
instantiated [ Ftext = object(Field) ] then it will contain all of the
fields of Field plus Len.  The code associated with Field can access the
extra field(s) (not known at compile time) through the identifier Extra.


  FieldPtr = ^Field;
  Field = object(Node)
    X, Y,
    Size   : Integer;                { size of the field in bytes }
    Title  : FStringPtr;
    Value  : Pointer;                { pointer to the value stored }
    Extra  : record end;
    constructor  Init(PX, PY, PSize : Integer; PTitle : FString);
    constructor  Load(var S : Stream);
    destructor   Done; virtual;
    procedure    Clear; virtual;
    function     Edit: Char; virtual;
    procedure    Show; virtual;
    procedure    Store(var S : Stream);
    procedure    Clean; virtual;
    procedure    Help;  virtual;
  end;

Field is an object of type Node and inherits all of the fields and methods
of Node.  Node contains only 1 field, Next_Item, which is a pointer to the
next item in the linked list (of nodes).  Node is an object of type Base,
which has no fields.

  NodePtr = ^Node;
  Node = object(base)
    Next_Item : NodePtr;
    Constructor Init;
    procedure Display ; virtual;
    function  First   : Pointer; virtual;
    function  Next (N : pointer) : Pointer; virtual;
    function  Prev    : Pointer; virtual;
  end;

  BasePtr = ^Base;
  Base = object
    destructor Done; virtual;
  end;




  Adding the 'Days_Meet' field to the form
  ----------------------------------------

A Days_Meet field is added to the form using the following statement.

  F.Add(New(Days_MeetPtr, Init(2, 7, 'Days Meet', 5)));

There is a lot going on in the above statement.  1) A Days_Meet object is
created, 2) the constructor Init is run, and 3) the item created is put
into the linked list designated by F.

{}

constructor Section.Init(PX, PY: Integer; PTitle: FString; PLen: Integer);
  begin
    Enrollment_Field.Init(PX, PY, PTitle, PLen);
  end;


constructor Enrollment_Field.Init(PX, PY: Integer; PTitle: FString; PLen: Integer);
  begin
    FText.Init(PX, PY, PLen + 1, PTitle, PLen);
  end;


constructor FText.Init(PX, PY, PSize: Integer; PTitle: FString; PLen: Integer);
  begin
    Field.Init(PX, PY, PSize, PTitle);
    Len := PLen;
  end;

There are 2 constructors for Field - Field.Init and Field.Load.  This one is
called by EnrolGen, which generates the database the first time.

constructor Field.Init(PX, PY, PSize: Integer; PTitle: FString);
  begin
    Node.Init;
    X := PX;                             { save X coord }
    Y := PY;                             { save Y coord }
    Size := PSize;                       { save size }
    GetMem(Title, Length(PTitle) + 1);   { getmem for field description }
    Title^ := PTitle;                    { save description }
    GetMem(Value, Size);                 { allocate storage for the value }
  end;                                   { the contents will be moved when Form.Put is called }


constructor Node.Init;
  begin
    Next_Item := nil;
  end;


I-4  Write the existing Form to the file
     ===================================

  Declarations
  ------------

For all of the items to work together, one must declare an instance of a
Form, a CardList and an EnrollStream.

    F: Form;
    C: CardList;  {see below}
    S: EnrollStream;

The following code initializes the CardList,

    F.Init(2, 3,  0, -1,' Enrollment Data Base ',False); {which appeared earlier.}
    C.Init(F.Size);                   { see below}
    S.Init(FileName, SCreate, 1024);  { initializes the stream }

    { write the forms using Form.Store }
    F.Store(S);

    { write # recs then the records }
    C.Store(S);   {see below}


Form.Store writes the information about the forms (prompts and field sizes)
to the disk.  Form.Store utilizes Stream Input/Output, another feature
included in Turbo Pascal 5.5.  Stream.Write allows the user to write to an
output device giving only the starting address of the data, and the number of
bytes to be written.  The statement:

  S.Write(X1,SizeOf(Integer)*5);

will write X1, Y1, X2, Y2 & Size, because they have been stored contiguously.

procedure Form.Store(var S: Stream);
  begin
    S.Write(X1, SizeOf(Integer) * 5);   { Write X1, Y1, X2, Y2, Size }
    S.Write(Shadow, SizeOf(Boolean));   { Write the shadow field }
    S.Write(Message,length(Message)+1); { Write the message }
    Fields.Store(S);
  end;

Fields.Store(S) is an instance of Field.Store, and the OOP design philosophy
comes into play in this routine.  The first two statements write X, Y and the
Header to the disk.

procedure Field.Store(var S: Stream);
  begin
    S.Write(X, SizeOf(Integer) * 3);               { Write X, Y, Size }
    S.Write(Title^, Length(Title^) + 1);           { Write the header }
    S.Write(Extra, SizeOf(Self) - SizeOf(Field));  { Write any other fields }
  end;


The third statement - S.Write - will write all the fields that are part of the
current instance.  As an example, consider the following declarations.

  Node = object(base)             { each object has a 2 byte overhead }
    Next_Item : NodePtr;          { pointer to the next node }
  end;

  Field = object(Node)
    X, Y,
    Size   : Integer;             { size of the field in bytes }
    Title  : FStringPtr;          { pointer to the field title }
    Value  : Pointer;             { pointer to the value stored }
    Extra  : record end;
    (plus all the methods associated with Field)
  end;

  FText = object(Field)
    Len: Integer;                    { Maximum length of the field }
    (plus all the methods associated with FText)
  end;

FText is an object of type Field, therefore it inherits all of the items
contained in Field (X, Y, Size, Title, and Value).  FText also contains
one more item, Len.  When Field.Store references Self, it is referring to
ALL of the fields defined in Field PLUS any fields in the current descendent.
If Self refers to an FText object, then SizeOf(Self) = SizeOf(Field) +
SizeOf(Len).   [ For the interested reader, the length of Node is 6 (4 bytes
for Next_Item, and 2 bytes overhead, for the VMT).  The length of Field is
20 ( 6 for Node, 2 each for X,Y & Size, 4 for Title, and 4 for value).  The
Length of FText is 22 (20 for Field and 2 for Len). ]

The statement...

    S.Write(Extra, SizeOf(Self) - SizeOf(Field));

... allows Field.Store to store fields contained in descendents of Field,
which contain fields that are NOT within the scope at compile time.  The
function, SizeOf, determines the value of its object at run time.


I-5  Declare the Card Form
     =====================

The declaration for the CardList is as follows:

  C : CardList;

where CardList is defined to be an object that has just three fields.
Count is an integer that contains the total number of records in the list,
DataSize has the size of just the data portions of the records, and Current
is a pointer to a CardNode.  The list is a circular linked list of CardNodes
that are not ordered in any fashion.  Current is a pointer that points to
the current node (begin displayed) in the list.


  CardListPtr = ^CardList;
  CardList = object(Base)
    Current : CardNodePtr;
    Count,
    DataSize:    Integer;
    { plus the methods associated with CarsList }
  end;



  CardNode = record
    Next: CardNodePtr;
    Prev: CardNodePtr;
    Data: record end;
  end;


I-6  Create the Card Form
     ====================

  C.Init(F.Size);  { sets Current = nil; Count := 0; & DataSize := F.Size }

  constructor CardList.Init(PDataSize: Integer);
    begin
      Current := nil;
      Count := 0;
      DataSize := PDataSize;
    end;


I-7  Write (an empty) Card Form to the Disk
     ======================================

The following code, in the data base creation program, will write number of
records then will write the records to the disk.

  C.Store(S);

C.Store refers to the method CardList.Store, which appears below.
CardList.Store writes Count (the number of records, which upon file creation is
zero) and DataSize (which containts the size of just the data portions of the
records) and then all of the records in the Data Base.  When the file is
being created, the loop will not be executed, because Count is zero.

procedure CardList.Store(var S: Stream);
var
  I: Integer;
begin
  S.Write(Count, SizeOf(Integer) * 2);

  { loop through and write each record }
  { When the file is created, Count is 0 }
  for I := 1 to Count do
    begin
      S.Write(Current^.Data, DataSize);
      Next;
    end;
end;

After CardList.Store has executed, the (empty set of) cards have been written
to the disk, and the Creation program terminates.  The next section describes
how the Update program reads in the cards.



II   Read in the existing Form from a file
     =====================================

In order to read the data base, the Forms must be read first, followed
by any existing data.  Both the forms AND the data are stored in (separate)
linked lists as they are read.

  II-1  Load the Forms
  II-2  Load the card information



II-1   Load the Forms
       --------------

Form.Load utilizes DosStream.Read (an overloaded version of Stream.Read)

Introduction to DosStream.Read
 - - - - - - - - - - - - - - -


DosStream.Read is a routine in the Stream.Pas unit, that allows the user to
read in 1 or more bytes into a memory starting at a given location.  The
statement...

  S.Init('filename', SOpen);

... opens the disk file 'filename' for input and associates it with S.  The
statement ...

  S.Read(address,number);

... will read, from the file associated with S, 'number' bytes into memory
starting at location 'address'.  The statement...

  S.Done;

... closes the file and dis-associates S with the file.


Using DosStream.Read to Load the Forms
- - - - - - - - - - - - - - - - - - -


The Forms are loaded using the following statement.

    F.Load(S);

The details of Form.Load are reasonably simple.  It uses an overloaded version
of Stream.Read, called DosStream.Read, to read in X1, Y1, X2, Y2, Size, &
Shadow.  Then it reads in the length of the string, followed by the string
itself.  (When strings are written to a file it first writes the value of
string[0], which is the number of characters in the string.)  In the last
line, it calls Fields.Load, described below.

constructor Form.Load(var S: Stream);
  var
    ch : char;
    i, Los : byte;
    st : string;
  begin
    S.Read(X1, SizeOf(Integer) * 5);
    S.Read(Shadow, SizeOf(Boolean));
    { read in the title string }
    S.Read(Los,1);
    Message[0] := chr(Los);
    S.Read(Message[1],Los);
    Fields.Load(S);
  end;

Since the Fields field is an object of type list, then the statement
Fields.Load(S);   will invoke the routine List.Load which loads the list
from a stream (in this case, the disk) one object at a time.

procedure List.Load(var S: Stream);
  var
    P: NodePtr;
  begin
    Clear;
    P := NodePtr(S.Get);
    while P <> nil do
      begin
        Append(P);
        P := NodePtr(S.Get);
      end;
  end;


The types of all nodes in the list to be loaded must have been registered
with the stream (so it knows what I/O routines to use).  The statement:
P := NodePtr(S.Get);    will invoke Stream.Get (which was written in Assembly
language).  Stream.Get will then invoke the appropriate routine.  For
illustration purposes, the routine in this example is Dept_Course.Load).
How does Stream.Get know which routine to use?  Answer - when the
records are written to the disk (using Stream.Put), the unique number
(associated with the object, Dept_Course) is written first, so that when it
is read in, the object number (associated with Dept_Course) will be read
first.  Then, S.Get will use the Load constructor for THAT object.  The Load
constructor will allocate space for the object on the heap, then read in the
rest of the data.

Stream.Put writes the specified object to the stream. The type of the object
must have been registered with the stream (using an overridden RegisterTypes
method).  Put writes a 16-bit object type identifier number onto the stream
and then calls the object's Store method, which writes a binary copy of the
object.  The 16-bit object type identifier corresponds to the index of the
object type in the TypeList and ProcList arrays.

Dept_Course is an object of type Enrollment_Field which is an object of type
FText which is an object of type Field.  Therefore Dept_Course inherits all
of the fields AND methods from Field, specifically Field.Load.  This routine,
Field.Load - shown below, uses Stream.Read to read in X,Y, Size, and Title.
The GetMem statement allocates storage for the value actually stored.

constructor Field.Load(var S: Stream);
{ Read in fields from the input stream }
  var
    L: Byte;
  begin
    S.Read(X, SizeOf(Integer) * 3);     { Read X, Y, Size }
    S.Read(L, SizeOf(Byte));            { Read Length of the Title }
    GetMem(Title, L + 1);               { allocate storage for the Title }
    Title^[0] := Chr(L);                { assign length of string }
    S.Read(Title^[1], L);               { Read in the string itself }
    GetMem(Value, Size);                { Allocate storage for the rest of the data }
    S.Read(Extra, SizeOf(Self) - SizeOf(Field));
  end;

The last line...    S.Read(Extra, SizeOf(Self) - SizeOf(Field));
reads in all of the extra data fields that a descendent object might have.
A Dept_Course object contains just 1 extra field, Len from FText.
Now, when Field.Load is compiled, the Dept_Course object just might NOT exist.
However, the SizeOf the object CAN be determined at run time, and the
S.Read will read in the value(s) for the additional field(s).

Referring back to List.Load, when the data has been read in, a pointer
to the object will be returned (and go through a type change) and be placed
into P.

The list is loaded by appending the result of S.Get to a list until S.Get
returns NIL.  The List.Append routine is given a more thourough treatment in
McDa92.


II-2  Load the card information
      -------------------------

The Card information (what one usually thinks of as a record) is loaded with
the following line.

    C.Load(S);          { load the card information }

The routine CardList.Load reads in the number of records, the size of the
records and then (in the For Loop) reads the records.  Insert;  creates a node
and puts it into the list (see below).  The S.Read statement will read in the
actual data.

constructor CardList.Load(var S: Stream);
var
  I, N: Integer;
begin
  Current := nil;
  Count := 0;
  S.Read(N, SizeOf(Integer));           { read in the number of records }
  S.Read(DataSize, SizeOf(Integer));    { read in the size of the records }
  for I := 1 to N do
    begin
      Insert;
      S.Read(Current^.Data, DataSize);  { read the data portion }
    end;
  Next;
end;

The Insert line, in the procedure above, refers to Cards_.CardList.Insert,
which is shown below.  It will 1) allocate enough storage to hold 1 entire
card, 2) insert the node pointed to by Current into the list, and then
3) point Current to the address of the node.

procedure CardList.Insert;
{ Allocate enough storage to hold everything, insert the node into a Circular
  Doubly Linked List, then set current to point to the address of the block.}
var
  N: CardNodePtr;
begin
  { allocate the space for the CardNode + the actual data }
  GetMem(N, DataSize + SizeOf(CardNode));

  { insert the node into the list }
  if Count = 0 then
    begin
      N^.Next := N;
      N^.Prev := N;
    end
  else
    begin
      N^.Next := Current^.Next;
      Current^.Next^.Prev := N;
      N^.Prev := Current;
      Current^.Next := N;
    end;

  { set Current to point to the space allocated  & increment the count }
  Current := N;
  Inc(Count);
end;


This concludes part II.  At this point, all of the card information and all
of the data have been read into memory.


III  Add, Update and Delete the Forms
     ================================

The Edit routine has the following logic.

   III-1  ˙ Put 1 card into the various fields      F.Put(C.CardData^);
   III-2  ˙ Display the fields                      F.Show(Start);
   III-3  ˙ Edit the fields                         F.Edit
   III-4  ˙ Put the fields back into 1 card         F.Get(C.CardData^);


III-1  Put 1 card into the various fields
       ----------------------------------


F.Put(C.CardData^);

When the data is read from the disk, it is stored in a contiguous record.
Form.Put will loop through and extract each field (from the contiguous
record) and put the field into the value^ portions of each node in the
linked list of fields.

procedure Form.Put(var FormBuf);
  var
    I: Integer;
    P: FieldPtr;
  begin
    I := 0;
    P := FieldPtr(Fields.First);
    while P <> nil do
      begin
        Move(Bytes(FormBuf)[I], P^.Value^, P^.Size);  {tricky}
        Inc(I, P^.Size);
        P := FieldPtr(Fields.Next(P));
      end;
  end;

The key statement is: Move(Bytes(FormBuf)[I], P^.Value^, P^.Size);
FormBuf is a typeless parameter that contains the address of a contiguous
record.  Bytes(FormBuf) results in a type change from a generic pointer to
a pointer to an array of bytes.  The following statement...

type
  Bytes = array[0..32767] of Byte;

... defines Bytes to be an array type of (up to) 32767 bytes.  An array is used
so that any displacement (from the base address in FormBuf) can be referenced
directly.  Since Bytes is a type and not a variable, the phrase ...

  Bytes(FormBuf)

... effectively overlays an array of bytes onto the contiguous buffer, making
the characters following FormBuf^ addressable using just a subscript.


III-2  Display the fields
       ------------------

The routine, Form.Show, will display 1 form, with a border, and all the fields
contained within that form.  (Since the code that draws the border is not
relevant to this paper, it has been ommitted.)  The routine traversses a
linked list of nodes (where each node contains 1 information portion), and
displays the contents of the the node.

  procedure Form.Show;
  var
    P : Pointer;
  begin
    P := Fields.First;
    while P <> nil do
      begin
        FieldPtr(P)^.Show;
        P := Fields.Next(P);
      end;
  end;

The line:    P := Fields.First;    will return the address of the first field
to be displayed and use the Show method of THAT field, to display the value.
Show, of course, has been overloaded (using the OOP philosophy) and P^.Show
refers to the current overloaded routine.  Fields.First and Fields.Next are
functions (that have been inherited from Node) the return a (generic) pointer
value.  The statement:    FieldPtr(P)    is a type change that will ensure
addressability of the methods in the object 'Field'.  There is no
Pointer^.Show but there is a FieldPtr^.Show, because FieldPtr points to a
field and there IS a method called Field.Show.


III-3  Edit the fields
       ---------------

The following routine traversses through the list editing the various fields
using the (overloaded) Edit methods of the different objects.  The statement:
    Ch := P^.Edit;  will invoke the Edit method associated with the object
pointed to by P.  The various Edit methods return a single character that
indicates further action - go on to the next field, go back to the previous
field or exit the edit mode.

function Form.Edit: Char;
  var
    P : FieldPtr;
  begin
    P := FieldPtr(Fields.First);
    repeat
      Ch := P^.Edit;  { P : FieldPtr }
      case Ch of
        CEnter,
        CNext: P := FieldPtr(P^.Next_Item);
        CPrev: P := FieldPtr(P^.Prev);
      end;
    until (Ch = CSave) or (Ch = CEsc);
    Edit := Ch;
  end;


The variable P is a pointer to a Field.  The code for Field.Edit follows:

function Field.Edit: Char;
  begin
    Abstract;  { display error message }
  end;

The reader can observe that there is NO useful code here.  This method was
designed to be overloaded with a more useful routine.  The routine is called
FText.Edit which appears below.


FText.Edit allows the user to edit an ascii field.  If the user keys in a
valid character, that character is appended on the the end of the string S.
If the user presses Enter, Up Arrow, Down Arrow, F2 or Escape then the current
field is 'cleaned' and displayed on the screen.  (The various versions of
'Clean' were written by the author and are described in more detail in
section V.)  If the user presses F1 then the Help method for the current
object is invoked.  (The various versions of 'Help' were written by the author
and are described in more detail in section V.)


function FText.Edit : Char;
  begin
    GetStr(S);  { get THIS field }
    repeat
      Ch := ReadChar;
      case Ch of
        #32..#255:
          begin
            if Start then S := '';
            P := 0;
            if Length(S) < Len then
              begin
                Inc(P);
                Insert(Ch, S, P);
              end;
          end;
        CEnter, CNext, CPrev, CSave, CEsc:
          begin
            if PutStr(S) then
              begin
                Clean;           << -- added by the author
                Show;
                Stop := True;
              end
            else
              begin
                Beep;
                P := 0;
              end;
          end;
        CF1 :
          begin
            Help;                << -- added by the author
          end
      else
        Beep;
      end;
      Start := False;
    until Stop;
    Edit := Ch;
  end;



III-4  Put the fields back into 1 card
       -------------------------------

Once the fields have been edited, then they are moved from the various objects
(where they were edited) back into the contiguous buffer using the same
typeless parameter (and the type change) found in Form.Put.

procedure Form.Get(var FormBuf);   { typeless parameter for the address }
{ move data from the value^ portions of each field to 1 contiguous buffer }
  var
    I: Integer;
    P: FieldPtr;
  begin
    I := 0;
    P := FieldPtr(Fields.First);
    while P <> nil do
      begin
        Move(P^.Value^, Bytes(FormBuf)[I], P^.Size);
        Inc(I, P^.Size);
        P := FieldPtr(Fields.Next(P));
      end;
  end;


This concludes part III.  At this point, all of the card information and all
of the data have been edited and is ready to write back onto the disk.



IV    Write the existing Form to the file
      ===================================

The procedure WriteCards will open a Stream for output (S.Init), write a
'signature' to the file, write the forms to the file, write the data to a
file, then close the file.  The signature is used to ensure that the format
of the file created matches the form expected by the updated.  The user
can change the signature number (if the structure of any objects are changed)
so that the proper update program is used with the files.

procedure WriteCards;
  begin
    S.Init(ParamStr(1), SCreate, 1024);
    if S.Status <> 0 then Error('Cannot create file');
    S.Write(Signature, SizeOf(Longint));
    F.Store(S);
    C.Store(S);
    S.Flush;
    if S.Status <> 0 then Error('Disk write error');
    S.Done;
  end;

This concludes the description of the Data Base as provided by Borland,
International.


V  The authors changes to the design
   =================================

The features added by the author were simple (but useful) routines related to
an Enrollment Data Base - Clean and Help.

The Clean procedures were written to clean up various fields as they
were being entered so as to conform to the style used in the semester schedule.
The idea was to allow the user to key in a rather loose form of the data, and
the clean method would, if it could, change the characters into a more
canonical form.  The following paragraphs illustrate some the cleaning that
takes place.


Dept_Course.Clean will accept abbreviations for the various departments, and
expand them to a 5 character form as shown in the following table.  (The 5
characters may include spaces.)

      b  ->  bio
      ch ->  chem
      cs ->  comsc
      f  ->  funrl
      g  ->  gensc
      m  ->  math
      n  ->  nursg
      p  ->  phy
      s  ->  stat

Also, this version of Clean will ensure that the department code is followed
by a 4 digit course number (and an optional letter, L or R).

      cs2112        ->   comsc2112         all departments get 5 positions
      b 2113l       ->   bio  2113L        lab is upper case L



Days_Meet.Clean will ensure that the user keyes in one of the letters
'm','t','w','r' or 'f' representing 1 or more days of the week.  Then it
returns a string with the day abbreviations spread out so that the day
of the week appears in the appropriate position - 'mwf' would appear as
'm w f' as shown below.

      mwf           ->   m w f             m,w & f are in positions 1,3 & 5
      tr            ->    t r              t & r are in positions 2 and 4
      mtrf          ->   mt rf
      mw            ->   m w

Start_Time_1.Clean, Stop_Time_1.Clean, Start_Time_2.Clean and Stop_Time_2.Clean
all invoke the same clean procedure.  This procedure determines whether the
class time falls in the morning or afternoon.  In the following example, the
clean procedure will determine that the given time refers to a morning class
and will add the 'am'.

      8:40   9:30   ->   8:40   9:30am

Instructor.Clean will accept (as few as) 2 characters to determine the
insturctor.  The user is required to keyin just enough information to make
the name unique.  With the current list of instructors, keying in the first
four characters of the last name is usually sufficient.

      sto           ->   w.  stockwell
      mcd           ->   w.  mcdaniel


The various Clean procedures are invoked when a particular field is selected
and the user keys in a value.  The statement...  Clean;   ... will invoke the
Clean procedure for the current object.  (See function FText.Edit in paragraph
III-3.)

It is recognized that inserting code into an existing object does not
follow the philosophy of oop programming, which states 'if the object does
what you want, then use it, if not then overload it'. However, it is the
authors belief that Help and Clean SHOULD have been part of the original
design and not just an extension added on by a user, therefore the code was
inserted.

The various Help procedures are invoked when a particular field is selected
and the user presses F1.  See function FText.Edit in paragraph III-3.
The Help procedures copy some help text from a file to the screen and will
not be discussed further.


Conclusion
----------

The programmers at Borland implemented an object oriented data base using
Turbo Pascal 5.5.  For the most part, the regular features of Standard Pascal
were used.  The current implementation did require the use of the following
procedures and concepts.  1)  Function 'SizeOf'  which returns the (current)
size of an object at run time, 2) typeless parameters that were introduced in
Turbo Pascal 3.0. (Re: procedure Form.Put(var FormBuf);), 3)  Type changes from
a generic pointer to an array of byte (Re: Move(Bytes(FormBuf)[I], st, 80))
which was introduced in Turbo Pascal 4.0,  4) DosStream.Read which allows N
bytes to be read into a data space, plus the standard OOP features
5) Polymorphism which allows 1 routine to access different routines with the
same procedure call, 6) Inheritance which allows an object to inherit fields
and methods from parent objects, and 7) Encapsulation which allows the
programmer to place data fiels AND procedure into a single object.

Bibliography

Cox91     Object-Oriented Programming.  Cox, Brad J, & Novobilski, Andrew J.
          Addison-Wesley.  1991.

Ezze89    Object-Oriented Programming in Turbo Pascal 5.5.  Ezzell, Ben.
          Addison-Wesley. 1989.

Haid89    Object-Oriented Turbo Pascal - A New Paradigm for Problem Solving
          and Programming.  Haiduk, H. Paul.  Mitchell Publishing. 1989.

Haid90    Object-Oriented Turbo Pascal - Problem Solving and Programming.
          Haiduk, H. Paul.  McGraw-Hill. 1990.

McDa92    Objects in Action.  McDaniel, Bill.  Conference on Applied
          Mathematics. University of Central Oklahoma.  Spring 1992.