                                   Lesson 9.

                             Pointers in Practice.
                     Structures and an example of their use.

  This lesson is a description and a demonstration of a doubly linked list
data structure. It is presented as a program which is - if such a thing
is possible - somewhat over commented.

  The "Points to note" about this offering are:

  1) It demonstrates the concepts of a doubly linked list.
     For simplicity the data object in this lesson is a string of characters.
     It is, of course quite possible to alter the data structure definition
     so that the the data pointer points at a different data type.

  2) The entire data structure is hidden from the user of the 
     functions, by making the structure which contains the pointers
     defined as "static". This has the advantage that "self appointed
     experts" cannot get at the data structure unless they alter the
     source code in the source file. In a practical production situation
     the permissions on the source would prevent this.
     It's the beginnings of object orienteering!

  3) The ends of the list are marked by NULL pointers in the
		 pointers.current->forward or pointers.current->back linking
		 pointers in the list as well as the pointers.head and pointers.tail
		 pointers being set to NULL by the functions when current is at either
		 head or tail.

  4) There is no need to have the definitions of either the pntrs,
     or the dl_node data structures in a header file as all the
     access to data is performed through the provided functions,
     which are listed here.

     For the whole story refer to the comments in the source.

  5) Have a look in the code and note all the special cases which
     have to be looked after. It was something of a revelation to me!

   dl_delete_node  Delete the current node.
   dl_destroy      Destroy the whole list.
   dl_get_data     Get the pointer to the data element.
   dl_insert_node  Insert a node into the list.
   dl_is_head      Is the current list element the head?
   dl_is_tail      Same for tail.
   dl_print_list   Print the whole of the list to stdout. ( Mostly for debug )
   dl_head         Set pointers so as to make current the head element.
   dl_tail         Set pointers so as to make current the tail element.

     Here is a little diagram which hopefully gets the concepts across.

 The Pointers.      The list of                 The data elements
                   linked nodes.

                +------------------+
                | NULL             |
                +------------------+   +------------------------------------+
       +------->| Data Pointer     |->-| "So let it be with Caesar."        |
       |        +------------------+   +------------------------------------+
       |        | Back Pointer     |
       |        +------------------+
       |            |          |                 
       |            ^          v
       |            |          |
       |        +------------------+
       |        | Forward Pointer  |
       |        +------------------+   +------------------------------------+
       |   +--->| Data Pointer     |->-| "The good is oft interred with ..."|
       |   |    +------------------+   +------------------------------------+
       |   |    | Back Pointer     |
       |   |    +------------------+
       |   +--+     |          |
  +----+----+ |     ^          v
  | Head    | |     |          |
  +---------+ | +------------------+
  | Next    |-+ | Forward Pointer  |
  +---------+   +------------------+   +------------------------------------+
  | Current |->-| Data Pointer     |->-| "The evil that men do lives .... " |
  +---------+   +------------------+   +------------------------------------+
  | Previous|-+ | Back Pointer     |
  +---------+ | +------------------+
  | Tail    | |     |          |
  +---------+ |     ^          v
       |   +--+     |          |
       |   |    +------------------+
       |   |    | Forward Pointer  |
       |   |    +------------------+   +------------------------------------+
       |   +--->| Data Pointer     |->-| "I come to bury Caesar, not to .." |
       |        +------------------+   +------------------------------------+
       |        | Back Pointer     |
       |        +------------------+
       |            |          |
       |            ^          v
       |            |          |
       |        +------------------+
       |        | Forward Pointer  |
       |        +------------------+   +------------------------------------+
       +------->| Data Pointer     |->-| "Friends, Romans, Countrymen, ..." |
                +------------------+   +------------------------------------+
                | NULL             |
                +------------------+

  This diagram illustrates the storage arrangements used in the test
program to store the famous text fragment as a doubly linked list. 
Note that the pointers in the pointer structure point at the node in the 
list as a whole and not specifically at the Data Pointer therein.
Please don't get confused if you look at the actual values with a debugger.

  Friends, Romans, Countrymen, lend me your ears;
  I come to bury Caesar, not to praise him
  The evil that men do lives after them;
  The good is oft interred with their bones;
  So let it be with Caesar.

               Thank you William Shakespere.

  In this case we have only five line of text, but it is more than possibe to 
have many thousands of elements in the list.

  First of all cut the program and header files out of the news file
and compile them with the pre-processor symbol "TESTING" defined on the
shell command line:-

$ cc -g -DTESTING -o dl_list dl_list.c
      ^
The -g flag makes the compiler output symbol table and source code line
number information into the output file for a debugger program to use.

Now set the program to execute.

$ ./dl_list

And press the SIGINT key as soon as the output appears so that unix
creates a "core" memory dump file.

$ sdb ./dl_list   ( You may have a different debugger on your machine. )

Put some suitable break points in the program and watch the pointers
change as the program statements are executed.

  For production use you can compile the program with the optimiser on
and the testing program removed.

$ cc -DENGLISH -c dl_list.c

  The prep. / homework exercise is to study this program carefully.
Appreciate that this code can implement the functions of these
data structures: A stack, a first in first out queue, & a sorted list.
Now think about making a program which uses these functions.

Yet another editor is also a possibility.

  Another more advanced exercise would be to write a function as part
of this file which sorts the list according to some defined criterion.
Hint: Use an external function which is called from within this suit of
programs using a function pointer.

  Refer to a good textbook on the subject of sorting and searching.

  I can recommend Donald Knuth: Art of Computer Programming.

	Refer to the C Users Journal, August 1993 - Vol. 11 No. 8, pp 53 - 58.
		This article by Roger Meadows is a good explanation of the testing
		techniques presented here.

  Note that whilst I have taken every care to try to ensure that this code is
both usable and free of bugs; it comes to you as a gift and like every thing
else which is free it is offered without any warranty whatsoever.

 /* -------------------------------cut here--------------------------------- */

#ident "@(#) dl_list.h - Header file.

#define ERROR (1)
#define SUCCESS (0)

#define FORWARD (1)
#define BEFORE (1)
#define STILL (0)
#define AFTER (-1)
#define BACKWARD (-1)

 /* -------------------------------cut here--------------------------------- */

#ident "@(#)dl_list.c - Routines for doubly linked list"

#if defined(TESTING)
# include <signal.h>
#endif

#include <stdio.h>
#include <errno.h>
#include <malloc.h>
#include <string.h>
#include <sys/types.h>
#include <assert.h>
#include "dl_list.h"

static char *error_messages[] =
{
#if defined(LANGUAGE)
# if ( LANGUAGE == ENGLISH )
  "head",
  "tail",
  "BEFORE the",
  "AFTER the",
  "\nYou cannot delete the",
  "node and leave current",
  "of the list\n",
  "Linked List Empty\n",
  "Direction must be either BEFORE or AFTER\n"
# endif
#else
  error "LANGUAGE must be defined"
#endif
  };

extern void perror();
extern void exit();

enum boolean { FALSE, TRUE };
typedef enum boolean BOOLEAN;

/*
** These structure definitions are in this file - as opposed to a header
** file because there is no need for the users of the routine to know
** anything about them. The list is accessed through the functions.
*/

struct dl_node
{
  struct dl_node *forward, *back;
  char *data;
  };

typedef struct dl_node DL_NODE;

/*
** I put all the pointers in a struct so that it is easy to examine them
** all at the same time with just one command to the debugger.
** It is not, for the operation of the program, strictly necessary to
** have previous and next pointers in this structure, but it certainly
** makes debugging so very much easier!
*/

struct pntrs
{
  DL_NODE *head,      /* Pointer to head of list. */
          *next,      /* Pointer to next, i.e. nearer to head, element. */
          *current,
          *previous,  /* Pointer to previous, i.e. nearer to tail, element. */
          *tail;      /* Pointer to tail of list. */
  };

static struct pntrs pointers =  /* Declared "static" so the hoi-poloi */ 
{                               /*  can't fiddle about with them!     */
  ( DL_NODE *) NULL,            /* This technique is "data-hiding"    */
  ( DL_NODE *) NULL,            /* Explicitly NULL for use as an      */
  ( DL_NODE *) NULL,            /* initialisation flag.               */
  ( DL_NODE *) NULL,
  ( DL_NODE *) NULL
  };

/*
** Set current to the head of the list.
*/

void dl_head()
{
  if ( pointers.current == ( DL_NODE * ) NULL )
  {
    ( void ) fprintf ( stderr, "dl_head: %s", error_messages[7] );
    exit ( ERROR );
    }
  if ( pointers.current == pointers.head ) return;
  pointers.next = ( DL_NODE * ) NULL;
  pointers.current = pointers.head;
  pointers.previous = pointers.current->back;
  }

/*
** Test to see if current is at the head of the list.
*/

BOOLEAN dl_is_head()
{
  return ( pointers.current == pointers.head ? TRUE : FALSE );
  }

/*
** Set current to the tail of the list.
*/

void dl_tail()
{
  if ( pointers.current == ( DL_NODE * ) NULL )
  {
    ( void ) fprintf ( stderr, "dl_tail: %s", error_messages[7] );
    exit ( ERROR );
    }
  if ( pointers.current == pointers.tail ) return;
  pointers.previous = ( DL_NODE * ) NULL;
  pointers.current = pointers.tail;
  pointers.next = pointers.current->forward;
  }

/*
** Test to see if current is at the tail of the list.
*/

BOOLEAN dl_is_tail()
{
  return ( pointers.current == pointers.tail ? TRUE : FALSE );
  }

/*
** Return the current data element.
*/

char *dl_get_data( d )
int d;
{
  switch ( d )
  {
case STILL:
    {
      break;
      }
case FORWARD:
    {
      if ( pointers.next == NULL ) return NULL;
      pointers.previous = pointers.current;
      pointers.current = pointers.next;
      pointers.next = pointers.current->forward;
      break;
      }
case BACKWARD:
    {
      if ( pointers.previous == NULL ) return NULL;
      pointers.next = pointers.current;
      pointers.current = pointers.previous;
      pointers.previous = pointers.current->back;
      break;
      }
    }
  return pointers.current->data;
  }

/*
** Insert a node into the linked list.
** p is a pointer to the data string you wish to add to the list.
** d is a direction indicator. BEFORE or AFTER the current position.
*/ 

void dl_insert_node( p, d )
char *p;
int d;
{
  DL_NODE *nn_p;
  char *nd_p;

  /*
  ** Allocate dynamic memory for node.
  */

  nn_p = ( DL_NODE * ) malloc ( sizeof ( DL_NODE ));

  /*
  ** Also for the data associated with the node.
  */

  nd_p = ( char * ) malloc ( ( size_t ) strlen ( p ) + 1 );

  /*
  ** Check that we could have it.
  */

  if ( nn_p == ( DL_NODE *) NULL || nd_p == ( char * ) NULL )
  {
    perror ( "dl_insert_list" );
    exit ( ERROR );
    }

  /*
  ** Copy the string of characters into the heap or dynamic memory.
  */

  ( void ) strcpy ( nd_p, p );

  /*
  ** First test to see if this is the first call.
  */

  if ( pointers.current == ( DL_NODE * ) NULL )
  {
    pointers.head = pointers.current = pointers.tail = nn_p;
    nn_p->forward = nn_p->back = ( DL_NODE * ) NULL;
    nn_p->data = nd_p;
    return;
    }

  if ( pointers.current == pointers.head && d == BEFORE )
  {
    pointers.previous = pointers.current;
    pointers.previous->forward = pointers.head = pointers.current = nn_p;
    pointers.current->back = pointers.previous;
    pointers.next = pointers.current->forward = ( DL_NODE * ) NULL;
    }
  else
  if ( pointers.current == pointers.tail && d == AFTER )
  {
    pointers.next = pointers.current;
    pointers.next->back = pointers.tail = pointers.current = nn_p;
    pointers.current->forward = pointers.next;
    pointers.previous = pointers.current->back = ( DL_NODE * ) NULL;
    }
  else
  if ( ( pointers.current != pointers.head ) && 
       ( pointers.current != pointers.tail )
       )
  switch ( d )
  {
case BEFORE:
    {
      pointers.current->forward = pointers.next->back = nn_p;
      pointers.previous = pointers.current;
      break;
      }
case AFTER:
    {
      pointers.current->back = pointers.previous->forward = nn_p;
      pointers.next = pointers.current;
      break;
      }
    }
  pointers.current = nn_p;
  pointers.current->forward = pointers.next;
  pointers.current->back = pointers.previous;
  nn_p->data = nd_p;
  }

/*
** Remove a node at current position.
** dir is the direction flag, it takes the value BEFORE or AFTER.
** BEFORE tells the function to move current closer to the head of the list.
** AFTER    "    "     ""    ""  ""     "      ""   ""  "  tail ""  "   ""
** Return ERROR if errors, SUCCESS otherwise.
*/

int dl_delete_node( dir )
int dir;
{
  DL_NODE *cn_p;
  char *cd_p;

  /*
  ** Check that there is something to delete.
  */

  if ( pointers.current == ( DL_NODE * ) NULL)
  {
    ( void ) fprintf ( stderr, "dl_delete_node: %s", error_messages[7] );
    return ( ERROR );
    }

  /*
  ** Check that they have given us a correct direction.
  */

  if (( dir != BEFORE ) && ( dir != AFTER ))
  {
    ( void ) fprintf ( stderr, "dl_delete_node: %s", error_messages[8] );
    return ( ERROR );
    }

  /*
  ** Save the pointers to the heap memory allocated by malloc.
  */

  cn_p = pointers.current;                  /* Pointer to linked list node. */
  cd_p = pointers.current->data;            /* Pointer to data area.        */

  /*
  ** "Re-connect" the pointers in the list and in pointers structure. 
  ** There are four different situations for which we have to cater.
  ** In order: 1) There is only one item in the list.
  **           2) The current item in the list is the first, 
  **           3)  "     "     ""  ""  "   ""  ""  "  last,
  **           4) The normal one with other items on either side.
  */

  /*
  ** Special condition 1: The current element is the only one.
  */

  if ( pointers.head == pointers.tail )
  {
    ( void ) memset ( &pointers, 0, sizeof pointers );
    }
  else

  /*
  ** Special condition 2: The current element is the head one.
  */

  if ( dl_is_head())
  {
    if ( dir == BEFORE )
    {
      ( void ) fprintf ( stderr,
                         "dl_delete_node: %d %s %s %s %s %s %s",
                         __LINE__,
                         error_messages[4],
                         error_messages[0],
                         error_messages[5],
                         error_messages[2],
                         error_messages[0],
                         error_messages[6]
                         );
      return ( ERROR );
      }

    pointers.head = pointers.current = pointers.previous;
    pointers.previous = pointers.current->back;
    pointers.current->forward = ( DL_NODE * ) NULL;
    }
  else

  /*
  ** Special condition 3: The current element is the tail one.
  */

  if ( dl_is_tail())
  {
    if ( dir == AFTER )
    {
      ( void ) fprintf ( stderr,
                         "dl_delete_node: line: %d %s %s %s %s %s %s",
                         __LINE__,
                         error_messages[4],
                         error_messages[1],
                         error_messages[5],
                         error_messages[3],
                         error_messages[1],
                         error_messages[6]
                         );
      return ( ERROR );
      }

    pointers.tail = pointers.current = pointers.next;
    pointers.next = pointers.current->forward;
    pointers.current->back = ( DL_NODE * ) NULL;
    }
  else

  /*
  ** This is the normal situation.
  */

  if ( !dl_is_head() && !dl_is_tail() )
  {
    pointers.next->back = pointers.previous;
    pointers.previous->forward = pointers.next;
    pointers.current = ( dir == BEFORE ) ? pointers.next : pointers.previous;
    pointers.previous = pointers.current->back;
    pointers.next = pointers.current->forward;
    }

  /*
  ** Return the dynamic or heap memory to the operating system.
  */

  ( void ) free ( cn_p );
  ( void ) free ( cd_p);
  return ( SUCCESS );
  }

/*
** Destroy the whole list and return all allocated memory to system.
*/

void dl_destroy()
{
  int direction = STILL;
#if(!defined(KILL_DIRECTION) || KILL_DIRECTION == FORWARD)
  dl_tail();
  while ( pointers.current != ( DL_NODE * ) NULL )
  { 
    direction = ( dl_is_head() ) ? AFTER : dl_is_tail() ? BEFORE : direction;
    ( void ) dl_delete_node( direction );
    }
#else
  dl_head();
  while ( pointers.current != ( DL_NODE * ) NULL )
  {
    direction = ( dl_is_tail() ) ? BEFORE : dl_is_head() ? AFTER : direction;
    ( void ) dl_delete_node( direction );
    }
#endif
  }
 
/*
** Print entire list in desired direction.
*/

dl_print_list(d)
int d;
{
  char * ch_p;

  if ( d == FORWARD ) dl_tail(); else dl_head();
  ch_p = dl_get_data ( STILL );
  do
  {
    ( void ) printf ( "%s\n", ch_p );
    ch_p = dl_get_data ( d );
    } while ( ch_p );
  }

#if defined(TESTING)
/*
** This is code to test the linked list functions.
** Leave it in the file even after the programs have been put
** into a production status. Mr. Murphy decrees that something will
** go wrong later! You might want to enhance things and break
** this code and if you have thrown away this test code you
** will only have to waste time recreating it!
*/

/*
** Signal handler so it is possible to 
** abort and dump core on receipt of a SIGINT signal.
** This means that it is possible to interrupt continuous
** loop bugs by pressing the the SIGINT key. A core image
** is also produced for later use by sdb or whatever.
*/

static void catch_signal()
{
  ( void ) abort();
  }

char *test_text[] =        /* Thank you William Shakespere. */
{
  "Friends, Romans, Countrymen, lend me your ears;",
  "I come to bury Caesar, not to praise him",
  "The evil that men do lives after them;",
  "The good is oft interred with their bones;",
  "So let it be with Caesar."
  };

#define NUMBER_OF_LINES ( sizeof ( test_text ) / sizeof ( test_text[0] ))

int main()
{
  char *nla = "********* New Line AFTER *********";
  char *nlb = "********* New Line BEFORE *********";
  char *nlh = "********* New Line BEFORE head *********";
  char *nlt = "********* New Line AFTER tail *********";
  char *err1 =
"dl_tail: line: %4d:\n\
      current is: %s\n\
         data is: %s\n"; 
  char *err2 =
"dl_get_data FORWARD line %4d:\n\
                  current is: %s\n\
                     data is: %s\n"; 
  char **ch_pp;
  char *ch_p;
  int i;
  BOOLEAN flag;
  char tmp = '\0';
  char *header = 
  "\n\nCreate a doubly linked list and print out the text as we go.\n\n";

  /*
  ** Setup SIGINT catch.
  */

  signal ( SIGINT, catch_signal );

  /*
  ** Create a doubly linked list and print out the text as we go.
  */

  ( void ) printf ( header );

  for ( ch_pp=test_text, i=0; i < NUMBER_OF_LINES; ch_pp++, i++ )
  {
    ( void ) printf ( "%s\n", *ch_pp );
    dl_insert_node ( *ch_pp, BEFORE );
    assert ( pointers.current->forward == pointers.next &&
             pointers.current->back == pointers.previous
             );
    }

  ( void ) printf ( "\n\nTesting commencing...\n\n" );

/*
** Set current to the tail of the list. Test it.
*/

  dl_tail();                        
  if ( pointers.current == pointers.tail )
  {
    ( void ) printf ( "dl_tail(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_tail(): ... FAILURE" );
    }

  if ( dl_is_tail() )
  {
    ( void ) printf ( "dl_is_tail(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_is_tail(): ... FAILURE" );
    }
  
/*
** Set current to the head of the list. Test it.
*/

  dl_head();                        
  if ( pointers.current == pointers.head )
  {
    ( void ) printf ( "dl_head(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_head(): ... FAILURE" );
    }

  if ( dl_is_head() )
  {
    ( void ) printf ( "dl_is_head(): ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_is_head(): ... FAILURE" );
    }

/*
** Set current to the tail of the list. Test it.
*/  

  dl_tail();
  ch_p = dl_get_data ( STILL );
  i = strcmp ( ch_p, test_text[0] );
  switch ( i )
  {
case 0:
    {
      ( void ) printf ( "dl_get_data ( STILL ); ... ok\n" );
      break;
      }
default: ( void ) fprintf ( stderr, err1, __LINE__-5, ch_p, test_text[0] );
    }

  flag = FALSE;
  for ( i = 1; i < NUMBER_OF_LINES; i++ )
  {
    ch_p = dl_get_data ( FORWARD );
    switch ( strcmp ( ch_p, test_text[i] ) )
    {
case 0:
      {
        break;
        }
default: 
      {
        flag = TRUE;
        ( void ) fprintf ( stderr, err2, __LINE__-5, ch_p, test_text[i] );
        }
      }
    }
    if ( !flag ) ( void ) printf ( "dl_get_data ( FORWARD ); ... ok\n" );

  if ( pointers.current != pointers.head )
  {
    ( void ) fprintf ( stderr,
             "current not at head when it should be, line: %d\n",
             __LINE__
             );
    }

  dl_head();
  ch_p = dl_get_data ( FORWARD );
  if ( ch_p == ( char *) NULL )
  {
    ( void ) printf ( "attempt to read data BEFORE head node: ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr,
              "attempt to read data BEFORE head node gave: %s\n",
              ch_p
              );
    }

  dl_tail();
  ch_p = dl_get_data ( BACKWARD );
  if ( ch_p == ( char *) NULL )
  {
    ( void ) printf ( "attempt to read data AFTER tail node: ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr,
              "attempt to read data AFTER tail node gave: %s\n",
              ch_p
              );
    }

  dl_head();
  flag = FALSE;
  ch_p = dl_get_data ( STILL );
  for ( i = NUMBER_OF_LINES - 1; i; i-- )
  {
    switch ( strcmp ( ch_p, test_text[i] ) )
    {
case 0:
      {
        break;
        }
default:
      {
        flag = TRUE;
        ( void ) fprintf ( stderr, err2, __LINE__-5, ch_p, test_text[i] );
        }
      }
    ch_p = dl_get_data ( BACKWARD );
    }
  if ( !flag ) ( void ) printf ( "dl_get_data ( BACKWARD ); ... ok\n" );

  ( void ) printf ( "\nTesting dl_insert_node...\n" );
  dl_head();
  ( void ) dl_insert_node ( nlh, BEFORE );
  for ( i = 0; i < 2; i++ ) ( void ) dl_get_data ( BACKWARD );
  ( void ) dl_insert_node ( nlb, BEFORE );
  for ( i = 0; i < 2; i++ ) ( void ) dl_get_data ( BACKWARD );
  ( void ) dl_insert_node ( nla, AFTER );
  dl_tail();
  ( void ) dl_insert_node ( nlt, AFTER );

  ( void ) printf ( "\nPrinting List with inserted lines...\n\n" );

  dl_tail();
  dl_print_list( FORWARD );

  ( void ) printf ( "\nRemoving lines just inserted...\n\n" );
  dl_tail();
  ch_p = dl_get_data ( STILL );
  do
  {
    if ( strncmp ( ch_p, nla, 8 ) == 0 )
    { int direction;
      direction = ( dl_is_head() ) ? AFTER : BEFORE ;
      ( void ) dl_delete_node( direction );
      }
    ch_p = dl_get_data ( FORWARD );
    } while ( ch_p );

  ( void ) printf ( "\nPrinting list after removal of inserted lines...\n\n" );
  dl_print_list( FORWARD );
  ( void ) printf ( "\nGoing to destroy list...\n" );
  dl_destroy();

  /*
  ** Check that pointers structure is all zero.
  */

  for ( i = 0, ch_p = ( char * ) &pointers;
        i < sizeof ( DL_NODE ) && tmp == '\0';
        i++, ch_p++
        )
  {
    tmp |= *ch_p;
    }

  if ( tmp == '\0' )
  {
    ( void ) printf ( "\ndl_destroy: List destroyed ... ok\n" );
    }
  else
  {
    ( void ) fprintf ( stderr, "dl_destroy: FAILED\n" );
    }
  ( void ) printf ( "\nTesting Completed...\n\n" );
  return (0);
  }
#endif

 /* -------------------------------cut here--------------------------------- */

  Enjoy!

Copyright notice:-

(c) 1993 Christopher Sawtell.

I assert the right to be known as the author, and owner of the
intellectual property rights of all the files in this material,
except for the quoted examples which have their individual
copyright notices. Permission is granted for onward copying,
storage, but not modification, of this course in electronic data
retrieval systems, and its use for personal study only, provided
all the copyright notices are left in the text and are printed
in full on any subsequent paper reproduction.

In other words you may pass it around to your friends and print it
out in full on paper, but you may not steal my text and pretend
you wrote it, change the text in any way, or print it as a bound book.

