
                                                               
                                                                           
                                                                         
                                  ////////    //    //    //   
                                 //    //    ///   //    //    
                                //    //    ////  //    //     
                               ////////    // // //    //      
                              //          //  ////    //       
                             //          //   ///    //          
                            //          //    //    ///////    
                                                                           
                                                                           
                                                                       
                                                                      
                                                                      
                                    Pascal NewsLetter         
                                         Issue #4
                                        July, 1990             
                                                                     
                                                                      
                                    Editor: Pete Davis         
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                      
                                                                          
                         The Programmer's Forum BBS is the home of
                         PNL. It can be reached in Washington, DC at
                         (202)966-3647. Information is available 
                         through the following locations:        
                                                                   
                         FidoNet:  Pete Davis@1:109/138           
                         Genie:    P.DAVIS5                          
                         BitNet:   HJ647C@GWUVM & UE356C@GWUVM
                         InterNet: HJ647C@GWUVM.GWU.EDU 
                                or UE356C@GWUVM.GWU.EDU
                                or Pete.Davis@f138.n109.z1.fidonet.org

                                  Table of Contents
                Introduction ..................................  3 (P.D.)
                Letters to the Editor .........................  5 (****)
                Binary Trees For Sorting ......................  7 (R.M.)
                Putting the Spurs to Pascal ................... 15 (J.R.)
                For Beginners ................................. 22 (B.G.)
                Review of TechnoJock's Turbo Toolkit .......... 27 (J.A.)
                Review of Turbo Pascal-The Complete Reference . 29 (P.D.)
            Key to author names:
            P.D. -> Pete Davis - Senior editor and writer 
            B.G. -> Bob Gowans - Staff writer
            J.R. -> Jan-Erik Rosinowski - Contributing writer
            R.M. -> Robert Mashlan - Contributing writer
            J.A. -> John A. Abele - Contributing writer
                                                                          3
                                     Introduction
               Well, PNL003 finally got off, so now I guess it's about time
          to get started on issue #4.  I was so pleased with PNL003 that  I
          was  worried I might break my arm  patting myself on the back. It
          was a good issue, though, and I was very pleased. I hope issue 4,
          and all the issues to follow for that matter, are just as good or
          better.
               I  received a wonderful letter  the other day  from a reader
          who said  he got a subscription  to the Cobb  Group newsletter on
          Turbo Pascal. He said that he and a friend compared it to PNL and
          said that they  preferred mine.  It's this kind  of support  that
          really makes me want to keep writing this.
               I hope that people will  keep up the contributions.  They've
          been a fantastic  support for me. Without  them, I don't  know if
          I'd be able  to go on  with this. There have  already been a  lot
          sent that I couldn't  have written myself and I think  that makes
          it more interesting for the regular readers.
               Because  of  the  time   consuming  nature  of  writing  the
          NewsLetter, I'm afraid I might be stuck with only putting out one
          issue every  two months or  so. I doubt  it would really  be much
          longer than that. I  am looking into getting some  more permanent
          writers who  I can count on  to write an article  for every issue
          and maybe even pitch in on  the editing. Right now, Bob Gowans is
          the only person who has had the  time to be able to contribute on
          a regular  basis. PNL is still  young, though, and growth  can be
          expected, but it  might slow down a  bit. Since the first  issue,
          the  PNL has grown very quickly,  but things seem to be steadying
          out a bit.
               Well, in this issue  we've got an article on Binary Trees, a
          very powerful data structure, which once understood, can be  very
          easy to implement. I had  already planned on doing an article  on
          Binary Trees,  but got a submission from  a reader on them. Since
          Robert's article covers the  basics of Binary trees, I  decided I
          would  take a  bit more  complex route  and discuss the  AVL tree
          balancing algorithm in a future issue.
               I've  also had a reader, John  Abele, who sent in a terrific
          review of the TechnoJock's Turbo Toolkit. I am doing a review  of
          Turbo Pascal  - The  Complete  Reference by  Stephen K.  O'Brien.
          These will  be only  the start  of reviews that  will be  done on
          books, toolkits, and software related to Pascal. 
               I think the  star of this issue is going  to be the PROFILER
          and accompanying article  that are  in this issue.  I've used  to
          profiler  a bit myself and have found  it very useful. I hope you
          like  it. Originally the article was written in German and thanks
          to the tremendous effort of one of my BBS users, Howard Sanner, I
                                                                          4
          was  able  to  get  the article  translated  to  English.  Howard
          suggested, and  I agreed, that it  was best to leave  the article
          using  some of the expressions that are distinctly German, in the
          article, instead  of trying  to write something  in English  that
          wouldn't  convey the meaning the author was trying to get across.
          I hope this poses no problems.
               All-in-all,  I  think this  issue  came out  very  well, and
          again, let me thank all those who contributed and hopefully I can
          get other readers to contribute also.
                                                                          5
                                Letters to the Editor
          From  uunet!GWUVM.BITNET!HJ647C
          From: uunet!CUNYVM.CUNY.EDU!CDCKAB%EMUVM1.BITNET (Karl Brendel)
          To:   Pete Davis <HJ647C@GWUVM.UCAR.EDU>
          Date: Fri, 27 Jul 1990 13:04 EDT
          Pete--
          I've just read the second issue of PNL (the first one I've seen).
          Thanks for this work!
               Not  wanting to be critical of article--being critical is so
          much easier  than writing--but Craig Thurber's  references to the
          $L  compiler directive is completely wrong, to the point that you
          really need to issue a correction in your next issue.
               Reference _Turbo  Pascal Reference Guide_ for 5.0 or 5.5, pp
          434-5 and 437-8  in my copy.  {$L filename}  is the "link  object
          file" directive. {$L- or {$L+ are  the "local symbol information"
          directives.  Craig  indicates that  {$L  filename}  is the  local
          symbol  information directive  and that  it  is ignored  if debug
          information  is off ({$D-}) (PNL002, pg 17). He shows an external
          file  linked as {$L+ HELLO.OBJ}.  This will not  work. It appears
          that Craig doesn't understand how to use the link directive.
               It appears to me that neither he nor you tried to compile or
          use  his  code. As  printed,  the  program "Hello"  will  neither
          compile  nor run.  One of the  compile errors  is from  a typo (a
          problem every  publication has), but  the other  is from  Craig's
          misuse of {$L+. I urge you to require your writers to compile and
          run every  single line of code  they send you, and  to repeat the
          compilation and running yourself before publication.
          Best wishes. I'm looking forward to PNL003.
                                          Karl Brendel
                                          Centers for Disease Control
                                          Epidemiology Program Office
                                          Atlanta, GA
          Karl, 
               You  bring up a  valid point. To  be honest, not  all of the
          code  that  goes into  the  articles themselves  has  really been
          checked by me to see if it works. I do read through the  articles
          and try to get an idea of the competency of the writer and see if
          the  ideas are valid. I must admit  that from time to time things
          will  slip  by, but  from  now on,  actual  code  going into  the
          articles will be tested.
               It appears  Craig had made a  small mistake and I  thank you
          for pointing  it out to the  readers. I am afraid  that this will
                                                                          6
          probably happen again, at  some point and hope that in the future
          readers  will respond, as you  have to make  the corrections that
          slip by us here at PNL.
                                              _Pete Davis
                                               Editor
                                                                          7
                               Binary Trees for Sorting
                                  By Robert Mashlan
               A binary  tree  is a  useful  sorting device  in  structured
          programming. It is  a quick and  easy way to  maintain data in  a
          sorted order.
               A  binary tree is composed  of units called  'nodes'.  These
          nodes are linked together  in a special  way to form  a tree.   A
          tree requires the use of pointers, and each node of the tree must
          be  allocated  dynamically. If  you  don't  quite understand  the
          concept of a pointer, don't worry, just read on.
               A pointer is  nothing more  than a variable  that holds  the
          address of another variable. Pointers  are used to access dynamic
          variables, that is, variables  that you can create or  destroy at
          will.   Besides  possessing this flexibility,  you may  hold more
          data with dynamic variables than you could by using global and/or
          local variables.
               In   a   program,   the   construction   of   pointers    is
          straightforward. In  the type  declaration of a  program consider
          the declaration
          Type
             stringptr = ^string;
          or in English, 'Let stringptr denote that all things of this type
          point to things of type string!'.  In the declaration  there is a
          caret  '^'  which simply  means  'pointer  to'.   It  takes  on a
          slightly different meaning when used in the actual code.
          Now we need to declare an instance of a pointer so that it can be
          used:
          Var
             sp : stringptr;
               What we have  done is to declare  an instance of  a variable
          that points to a string.   This variable doesn't take much space;
          pointer in Turbo Pascal it is 4 bytes.
          Now we need to allocate storage for a string on the heap:
          begin
              New(sp);
               What we have done here is  told a set of routines called the
          'Heap Manager'  that we need some  space for a string.   If there
          are 255  bytes in a row,  the Heap manager complies  and sets the
          value  of sp to the address of the  space that it gave us for the
          string.  This space will be  protected from being  given to other
                                                                          8
          requests for space until the space is reclaimed with dispose.
          Now  that we have allocated space for it, we can now use it.  The
          caret '^'  comes into play again as it is needed to dereference a
          pointer.  This means 'refer to the thing I point at'.
              sp^ := 'I am a dynamic string!';
              writeln(sp^);
               Now when we no longer need the string, we can get rid of it,
          possibly reusing the space that  it occupied.  This is  done with
          the dispose function:
              dispose(sp);
           end.
               After using dispose,  the value that sp had is now no longer
          valid.  That  means don't read it, don't change  it, don't use it
          for  anything unless  you reallocate  space for  it with  the new
          procedure.   A pointer with  an invalid value that  is being used
          often causes really  mean bugs.  It may  write to almost anywhere
          in memory, like the next instruction to be executed, the IDE, the
          operating  system,  etc.   The  problem  is  that  these kind  of
          problems may not show up until  much later, such as when you exit
          your program and run another.
               You should also try  not to lose track of  your pointers, or
          you'll get a 'memory leak' that  wastes space that could be  used
          for other purposes.  Whenever a  pointer goes out of scope always
          dispose the space it points at.
               A pointer can also have the special value nil.  This  simply
          means that the pointer points at  nothing.  It can be assigned to
          any pointer.
          Ok, now on to the forest:
          First a graphical demonstration of creating a binary tree:
          Say we have these pieces of data and we want to sort them in
          alphabetical order:
          G, B, A, H, I, E, J, F, C, D
          The first letter G, we will denote as the root of our tree:
                                  G
                                 / \
          The node that G is in points to other nodes, which are
          currently pointing at nothing.
                                                                          9
          Now we add the next letter B.  To add it, we use the algorithm:
          1) Start at the root node
          2) if the node is empty then
                place the data here
             else
                if the data is greater than the data at the node then
                   go to the right node
                else
                   go to the left node
                go to step 2
          By using this algorithm, we now have
                                 G
                                / \
                               B
          ( Unused datum: A, H, I, E, J, F, C, D )
          The next step would be:
                                 G
                                / \
                               B
                              /
                             A
          to
                                  G
                                 / \
                                B   H
                               /     \
                              A       I
          to
                                  G
                                 / \
                                B   H
                               / \   \
                              A   E   I
          to
                                  G
                                 / \
                                B   H
                               / \   \
                              A   E   I
                                       \
                                        J
                                                                         10
          to
                                  G
                                 / \
                                B   H
                               / \   \
                              A   E   I
                                   \   \
                                    F   J
          to
                                  G
                                 / \
                                B   H
                               / \   \
                              A   E   I
                                   \   \
                                    F   J
          to
                                  G
                                 / \
                                B   H
                               / \   \
                              A   E   I
                                 / \   \
                                C   F   J
          to
                                  G
                                 / \
                                B   H
                               / \   \
                              A   E   I
                                 / \   \
                                C   F   J
                                 \
                                  D
               Now we have a strange looking upside down tree that seems to
          have no reason to it.   To use this tree, we must  transverse it.
          This is the name of  a special algorithm that we use  to 'travel'
          through the  tree so  that it comes  out in sorted  order.    The
          traversal algorithm:
          1) start at the root node
          2) do procedure 2
          3) end
          4) if node is not empty then
                do this procedure with the left node
                process the data at this node
                do this procedure for the right node
                                                                         11
               Using  this algorithm  with the  tree we  start at  the root          node. The instructions tell us to go left, so we go to B. Next we
          go to A.   Since there is no left node to A,  we process A, or in
          this case, write it  down.  We now return to B and print it.  The
          instructions say go right, so we end up at  E.  Now we go left to
          the C  node and print it.   Mentally traversing the  rest of this
          tree is left as an exercise to the reader.
               The traversing  algorithm is called recursive,  that is, the
          algorithm calls itself.
               If you have a good mental picture of what a tree is, read on
          for a demonstration of implementing it in Pascal.
          ----------------------
          Program TreeSort;
          Const
             MaxStr = 255;  { The maximum length of strings to be used }
          Type
             Str = string[MaxStr];  { Our string type to be used }
          { We now declare a node record and pointer type: }
             nodeptr = ^node;
             node = record
                data : str;
                left, right : nodeptr;
             end;  { record }
          {  The node record  type has three fields.  The first field data,
          is used to  store a string, the things we are  to sort.  The left
          and right fields  contain pointers  to other nodes.   When  these
          nodes aren't pointing at  other nodes, they will be  assigned the
          value nil.   }
          { Next we must have a root to our tree: }
          Var
             root : nodeptr;
          { Root is not actually the root, but a pointer that will point to
            the root node of our tree. }
             InData : str;
          { This variable will be used by the main block of this program }
                                                                         12
          Function NewNode( var newdata : str ) : nodeptr;
          { The purpose of this function is to allocate and and initialize
            space for a node.  The newdata parameter is the string that
            will be placed into the Node's data field.  newdata is passed
            by reference to reduce stack usage. }
          var
            result : nodeptr; { the value to be returned }
          begin
             New(result);  { allocate space for the new node }
             with result^ do
             begin
                left := nil;  { set these pointers to point at nothing }
                right := nil;
                data := newdata; { store the data in the node }
             end;  {with}
             NewNode := result;
          end; {Function NewNode}
          Procedure Add( var newdata : str );
          {   This procedure is  an implementation of  the 'word' algorithm
          used
             to add data to the graphical tree.  The string that is passed
             as a parameter will be stored in the tree.  }
          var
             chase : nodeptr;  { this pointer is used for }
                               { moving through the tree  }
             done : boolean;   { the done flag }
             Function Compare( var a,b : str ) : boolean;
             { This nested function tells the outer procedure the order
               that the data is to be sorted. It is to return TRUE is a is
               to go to the left of b.  In this case we are comparing
               strings in alphabetical order with no regard to case  }
             var
                i, limit : integer;
             begin
               if length(a) < length(b) then  { limit is to be the maximum}
                  limit := length(a)          { number of times to loop,  }
               else                           { In this case, the length  }
                  limit := length(b);         { of the shortest string.   }
                i := 1;
                while ( i <= limit ) and ( Upcase(a[i]) = UpCase(b[i]) ) do
                   i := succ(i);  { Increment i }
                { at this point either a mismatch occured or the strings   
                                                                         13
                  Matched up to the length of the shortest string         }
                if UpCase(a[i])=UpCase(b[i]) then
                   Compare := length(a) > length(b)
                      { longer strings go after shorter ones }
                else
                   Compare := UpCase(a[i]) > UpCase(b[i]);
             end; {Function Add.Compare}
          begin
             if root = nil then
                root := NewNode(newdata)  { gotta start somewhere }
             else
             begin
                done := false;
                chase := root;  { start at root }
                repeat
                   with chase^ do
                   begin
                      if Compare( data, NewData ) then {go to the left}
                         if left = nil then
                         begin                         {put the data here}
                            left := NewNode(newdata);
                            done := true;
                         end
                         else
                            chase := left    { move on to the left node }
                      else                      { go to the right }
                         if right = nil then
                         begin                      { put the data here }
                            right := NewNode(newdata);
                            done := true;
                         end
                         else
                            chase := right;   { move on to the right node }
                     end; {with}
                until done;
             end; {else root=nil}
          end; {Procedure Add}
          Procedure Transverse( np : nodeptr );
          { This is the transversal procedure to the tree.  It calls the
            nested function Process in sorted order. Note that this
            procedure is recursive.  }
             Procedure Process;
             begin
                writeln(Output, np^.data);{write data to standard output}
             end; {Procedure Transverse.Process}
          begin
                                                                         14
             if np <> nil then
                with np^ do
                begin
                   Transverse(left);
                   Process;
                   Transverse(right);
                end; {with}
          end; {Procedure Transverse}
          {Pretty short, huh?}
          begin {Program TreeSort}
          { This program is a filter, that is, it takes input from
            standard input, sorts it, and send it to standard output.
            It works like the sort filter you get with MS-DOS            }
             root := nil;  { initialize Root }
             while( not eof(Input) ) do   { read all the input datum }
             begin
                readln(Input,InData);
                Add(InData);
             end;
             Transverse(root);
             { transverse the tree and write out the datum }
          end. {Program TreeSort}
          ----------------------
          To use this program, create an ascii text file of strings to
          sort.  Type in the command:
          >treesort < data.dat
          and the sorted output will be displayed.
               Binary trees  are useful for many  sorting applications, and
          are fairly quick.  There are a few disadvantages to binary trees,
          namely the recursive nature  of the transversal algorithm.   In a
          case where the incoming  data is nearly or  already sorted.   The
          tree  will appear  to look  like a  linked list,  or a  very long
          chain.   This will cause the transversal algorithm to call itself
          many times and possibly cause a stack overflow error.
            That's  it.  If you have in  questions or comments, you may ask
          them  in the  FidoNet International  Pascal Echo  or via  FidoNet
          NetMail to Robert Mashlan @ 1:147/34.33
                                                                         15
                             PUTTING THE SPURS TO PASCAL
                                by Jan-Erik Rosinowski
               Does it un-nerve you when, with increasing development time,
          your program runs progressively  slower? Editors that cannot keep
          up with the cursor? Databases that  seem to be stored on cassette
          tape? Though the complexity of the problem can cause  bewildering
          frustration,  it is constructive to use a profiler at this point.
          It  will expose  the time-consuming  procedures, so  that nothing
          will stand in the  way of subsequent significant  optimization of
          the program. Such a profiler for Turbo Pascal v. 4 and 5 programs
          is described below.
               There  are  two  approaches  to  program  optimization,  the
          theoretical  and  the  practical.   They  will  both  be  briefly
          sketched.
                     PROGRAM OPTIMIZATION IN THEORY AND PRACTICE
                         
               The  theorist  uses the  following  method:  using a  simply
          canonical approach,  he  searches  for  the  theoretically  lower
          limits of the best algorithm. The time to formulate and verify an
          algorithm  is  limited  only  by  the  author's  maximum  writing
          speed--at the latest,  after a midnight  snack of pizza he  is in
          the  know. For him, the implementation of the algorithm is almost
          secondary: This would require  him to write a small  program, and
          in such a manner could many free hours and years pass!
               In  contrast,  the  graceless  practitioner: He  writes  his
          database program  using a BASIC interpreter.  Before thinking two
          minutes  about the  program, he  has written  100 lines.  After a
          hundred  test runs  that help  him in  the "verification"  of his
          program,  he  has  a  bug  free  version.  He  often  thinks  the
          following: The program's done.  Only he can no longer  coordinate
          the long coffee  break his  computer's run time  has forced  upon
          him.  After a compiler doesn't  help him, he  decides to re-write
          the program in assembler. For quick  access to his data, he  once
          again uses a linear, sequential search.
                              AND HOW ONE DOES IT RIGHT
               Though  both examples  were  exaggerated, it  is clear  that
          neither approach  is workable.  Clever optimization of  a program
          comes after a careful analysis of the problem and costs. Then one
          must next analyze the kernel functions of the planned program. In
          a database  program, these  would be,  for example, functions  to
          insert,  update, delete,  search, and  index data  records. Along
          with the expected quantity and format of data and operations, one
                                                                         16
          also selects  appropriate data structures. Thus  one chooses here
          suitable algorithms, for  example, linear searching,  B-trees, or
          hashing. An  estimation of  the expected overall  complexity, the
          provision of resources (time, money, tools), and taste/experience
          will quickly determine the high or low-level programming language
          to be used.
               After  solving the  general complexity  of the  problem, one
          should, in  the first, analytical phase, in no case underestimate
          the time taken up by operating system functions. In many programs
          they claim a large part of the total run time.  Bypassing DOS and
          BIOS  calls will definitely yield a copious increase in speed, at
          the cost of  many gray hairs when porting the  program to another
          system. Routines with extreme requirements, for example, a screen
          handling package for a text program, will need to be purchased.
                                      AND THEN!
               After choosing appropriate  algorithms, data structures, and
          programming  languages,  places  ripe  for  optimization  can  be
          quickly identified. There  are, of course,  many sloppily-written
          small routines that  are called unexpectedly often.  If these are
          optimized first, relatively small  optimizations will yield a big
          improvement  in  run  time.  Routines  should  be  re-written  in
          assembler  or,  for  example,  a  bubble  sort  replaced  with  a
          quicksort.
               The amount of improvement to be gained from using a profiler
          is inversely proportional to  the amount of thought put  into the
          original  design. In other words,  the greater effort  put into a
          statement, analysis, and evaluation of the problem, the fewer the
          opportunities  for   improvement.  The  true  art   of  efficient
          programming thus  lies in  appropriate decisions in  the analysis
          and coding phase.
                                       PRACTICE
               With the profiler's help,  you can quickly do away  with the
          worst time wasters in a Turbo Pascal program. The program package
          consists of a preprocessor (PROFILER.PAS) as well as the analysis
          unit, PROFILE.PAS. The  work of  the preprocessor is  to imbed  a
          call  to "PBegin"  and PEnd"  at the  beginning and  end of  each
          function, respectively. (See listings 4 and  5.) When the program
          runs, these procedures capture timing statistics. Both the format
          and  use  of  upper  and  lower  case  in  the  source  code  are
          unimportant. Include files and source code of available units are
          both  processed. In  addition, the  Pascal ugliness  of "Record,"
          "Case," and "End" (in  contrast to Modula-2 or single  logic only
                                                                         17
          "End") is digested without complaint.
               The  programs were  developed under  Turbo Pascal  5.0. Only
          changes to a few CONST declarations and case selectors are needed
          to  adapt  it  to  the  previous  version.  The  assembler  part,
          PROTIMER.ASM, was assembled with MASM.
                                      WILLY GO!
               The preprocessor  "PROFILER" runs only from  the DOS command
          line. The first  parameter expected  is that of  the name of  the
          module to be analyzed. This  can be either one unit or  the whole
          program. Modules linked  in with "uses" or include directives are
          each processed. If  one wants to  omit a module  (e.g., one  that
          exclusively  reads the keyboard via  the BIOS), one  can name the
          file to be excluded after the /X: option. Names without extension
          are interpreted as ".PAS,"  and this applies to all  source files
          (thus  units  to be  excluded with  "/X:  XYZ.TPU" will  cause an
          error).
               In order not to inflate the profiler's size, it assumes that
          the  program to  be processed  has no  syntax errors.  This isn't
          really a restriction, since  the profiler is intended to  be used
          to  put the finishing touches  on the program.  The profiler will
          also not be  of help to  people who, as already  mentioned above,
          plan  their  programs  in the  coding  phase.  The  module to  be
          profiled  must   always  have   either  a  "Program"   or  "Unit"
          identifier.
                                   THE BIG RENAMING
               So that one  can't work  with source code  the profiler  has
          processed  (the  time checking  calls will  not  be found  in the
          finished program),  the files to  be processed are  renamed. Thus
          the first and last letters of the extension are exchanged (".PAS"
          becomes ".SAP"). This can nevertheless lead to problems  should a
          program be run through the profiler  again, as well as with files
          like,  for example, "INCLUDE.010." The  latter must be renamed to
          something reasonable. The PAS-SAP  collision is best avoided with
          a  batch file that will  copy the identically  named ".SAP" files
          over the ".PAS" files. The latter may then be deleted.
                          INTERPRETING THE RUN TIME PROFILE
               After one  has compiled  the profiler-processed  program and
          tested it adequately, the unit PROFILE will generate the run time
          analysis  [PROGRAM/UNIT  NAME].PRF  from  the  file [PROGRAM/UNIT
                                                                         18
          NAME].PR$  produced by the profiler.  In the first  column is the
          procedure name,  in the second  the number  of calls, and  in the
          third column the  run time  in milliseconds. In  contrast to  the
          notion of run time in  reference (1), here it means only  the run
          time of  the procedure itself.  It does not  include that of  the
          subprocedures it  calls. This  considerably simplifies  usage, so
          that  one can  directly see  the true  time a  procedure consumes
          (figure  1). By means of the DOS  SORT program one can generate a
          list by run time, so that one can immediately find the slow parts
          of a large program.
               The  definition of run time in reference (1), in contrast to
          that given here,  always shows  100% for the  main program's  run
          time, which is true in few cases.
               A marginal note: Anyone who  tests the entire program  being
          analyzed  with  the profiler  and  gets  unexpected results  must
          remember that the  time the  profiling unit itself  takes can  be
          compensated for by  setting the variable "adjusttimer"  to a more
          precise value. The true run time of the program is small compared
          to that of the stopped.
               Should  one wish  to test  the program  isolated  in diverse
          situations, one can delete  the uninteresting procedures from the
          ".PR$" file with a  text editor. (Delete whole lines.)  This will
          greatly  reduce the  confusion of paper,  since the  profile unit
          will  only emit a run  time profile for  procedures in the ".PR$"
          file.
               Should  the program  terminate before the  "End." statement,
          there will nevertheless be a run time analysis. In  this case the
          profiler will issue a corresponding warning. This can happen, for
          example, if the  profiler is  used to analyze  itself, since  the
          main  program terminates with a  halt statement. The  run time of
          the "open" procedure will be approximate.
               One  small  piece  of  bad  news:  External  procedures  (in
          assembler or C, for example) cannot be directly analyzed. If this
          is desired, one  must enclose  them in an  outer procedure.  This
          presupposes changes to the external procedures (public names); an
          extension of  the profiler  in this  regard would be  excessively
          complex.
                                      INTERNALS
          The  profiler  consists  of   two  kernel  procedures:  one,  the
          recursive function  "prep_module" that  inserts the calls  to the
          profiler. It  is recursive,  since it  calls itself in  analyzing
          uses  and  include modules.  This  serves  to simplify  the  file
          management,  since thus Turbo  Pascal gets  stuck with  the dirty
                                                                         19
          work.  The  second  important  procedure,  "getsymbol,"  supplies
          "prep_module" with  tokens and handles include  modules. That the
          rest of allocated storage isn't released should not  trouble you;
          DOS also  takes no notice of it,  since the management is handled
          internally by Turbo Pascal.
               The  most   important  function  of  the   unit  PROFILE  is
          "readtimer."  It makes  possible accurate  timing at  microsecond
          intervals; it thus has  substantially greater resolution than the
          BIOS  or DOS timers.  In spite of  what the manuals  say, the DOS
          timer cannot resolve time finer than  five milliseconds. The more
          precise  results  of  the  "readtimer"  procedure  are  also  not
          surprising,  when we consider the fact that the BIOS timer, which
          is  also used by  DOS, is incremented whenever  the timer used in
          the  "readtimer" procedure counts down to zero. Timer zero of the
          8253 chip acts as time keeper. It is critical in this  connection
          to coordinate with the 8259 interrupt controller; under the worst
          circumstances it can  falsify the measurements "catastrophically"
          (by five milliseconds).  This can  happen if the  timer has  been
          latched  and  an  interrupt is  triggered  between  the time  the
          counter value  is stored and  the interrupt  request register  is
          read.  An extremely low counter value indicates that request flag
          has been set. Since this is nevertheless influenced by the timer-
          independent CPU  frequency, an adjustment of  the constant "corr"
          in PROTIMER.ASM is needed. Also,  the DRAM refresh and  installed
          cache programs (DOS buffers,  FASTOPEN, VCache, etc.) falsify the
          measurement not inconsiderably. For these reasons it is important
          to  keep the timer resolution  under 1/100000 seconds  and not to
          put the unit  PROFILE into  an overlay. The  constant "fracs"  in
          PROFILE.PAS determines the number of decimal digits to  which the
          run time in milliseconds will be displayed.
               The unit  PROFILE will  normally be  the first  unit entered
          once the program is  started and the last left (via an Exitproc).
          The counter  is installed,  and  the correction  value needed  to
          account for calls to "PBegin" and "Pend" calculated, via calls to
          the initialization blocks. The local stack as well as the storage
          space  "procstore"  are statically  declared,  and  the constants
          "stacksize" and  "maxprocedures" must be adjusted. Dynamic memory
          allocation  in the profiler might cause errors, since the program
          to  be analyzed  could  also  manage  its  storage  by  means  of
          MARK/RELEASE or also DISPOSE.
               Listing  5 is  presented as an  example of  capturing timing
          information.  Time measurement  begins  with PBegin  (1).   Since
          nothing is on the stack yet, PBegin stores only the number of the
          active  procedure (1) as  well as  the system  time. The  call to
          PBegin (2) in the procedure "a" causes the time used in procedure
          1 (stored  time-elapsed time-  correction value) to  be saved  in
          "procedure[1].time." Again the number of the active procedure (2)
          and the  system time are pushed  on the stack. PEnd  performs the
          analog:  it closes  off the  active procedure  and then  pops its
                                                                         20
          record  off the stack. Since the system  time is once again read,
          the second  call to PEnd can  determine the run time  of the main
          program correctly.
                                  LIMITATIONS OF USE
               Use  of the profiler can  lead to problems  in the following
          circumstances:
          1. As mentioned above, extra steps are needed
             to analyze procedures written in other languages.
             They must be given different public names so that
             one can write enclosing procedures with their
             original names. If only a few modules are involved,
             it may be quicker to change the names in the Pascal
             source.
          2. Programs or PCs that themselves make use of
             timer zero can cause minor irritations and
             influence the unit PROFILE, respectively. In this
             case, the procedure READTIMER (PROTIMER.ASM) must
             be changed.
          3. The run time analysis is only procedural.
             The time taken by DOS and run time library calls
             must also be accounted for. One would be well
             advised, for example, to redefine the file
             functions in their own unit. "READ" and "WRITE"
             statements unfortunately often evade this method.
             In individual, truly critical cases, one can extend
             the ".PR$" file with a pseudo-procedure after the
             last line. It will then carry all the PBegin and
             PEnd calls associated with the "READ" and "WRITE"
             statements.
          4. Time critical procedures, as, for example,
             a serial port handler, will take longer to run, and
             thus the profiler may interfere with their
             operation. For this reason, interrupt procedures
             are automatically excluded from analysis. One must
             either delete the PBegin and PEnd statements from
             time critical procedures or put them in units or
             include modules that are excluded with the /X
             directive from analysis.
          5. "Inline" procedures cannot be analyzed with
             any tricks. This corresponds to their character.
             The shortest machine programs execute in a fraction
             of a microsecond. Anyone who would like to analyze
             such a time critical section should perhaps
                                                                         21
             consider coding it completely in assembler.
               With these hurdles  crossed, nothing  stands in  the way  of
          analyzing your critical source  code. One should, however, always
          put  readability and correctness  of a program  before its speed,
          otherwise one can program the same as in C or BASIC.
   
                         References
                         
                         1. Profilgewinn, c't 5/89.
                         2. Interrupts auf Reihe, 5/86.
                         3. Die PC-Referenz 
   

                          Figures
                         1. PROTEST.PRF: This is the result of the analysis
                            of PROTEST with the unit PROFILE.

                         Listings
                         
                         1. PROFILER.PAS: This program links calls into
                            Turbo Pascal 4/5 compatible source code that
                             make possible an analysis accurate to the
                             microsecond.
                         2. PROFILE.PAS: This unit measures the procedure
                            run times and generates a run time profile from
                            them.
                         3. PROTIMER.ASM: This procedure can read the timer
                            with microsecond precision.
                         4. PROTEST.SAP: The program to be analyzed
                            appeared thus before the application of the
                            profiler.
                         5. PROTEST.PAS: The profiler has here performed
                            all of its work.
                                                                         22
                                    For Beginners
                                    By Bob Gowans
               In  the last issue of PNL the beginners column discussed the
          use of the Pascal assignment operator ( := ) to  assign values to
          variables.  We  were  concerned  mainly with  operations  on  the
          variable data type integer,  a simple Pascal data type,  and were
          made aware of other  Pascal data types such as char,  Boolean and
          real. In this  issue we  will further increase  our knowledge  of
          variable   operations   by  designing   a   simple   program  and
          implementing our design  as a  Pascal program.  In particular  we
          will  be  paying close  attention  to  correct initialization  of
          variables and input and output.
               Later you will be  presented with a simple problem  and from
          this we will devise a design to solve this problem but first lets
          discuss the technique by which we may solve the problem. First we
          must make sure that  we fully understand the problem.  How?... By
          asking relevant questions so that we are  made fully aware of all
          the details  of the problem.  For example,  if we  were asked  to
          update  a catalogue  of books  we  might ask  How many  books are
          there?  Are they to  be ordered? How  are they to  be ordered, by
          title or by author? You can probably think of many more questions
          to  ask  until  you  are  fully  satisfied  that  you  thoroughly
          understand the problem.
               Next we would  start to  devise a solution  keeping in  mind
          that our ultimate aim is to implement the solution to the problem
          by  making use of a computer. Devising an efficient solution when
          using  a computer  is  something that  comes  about as  you  gain
          experience of your computer system's capabilities, its advantages
          and disadvantages.
               One  of the best methods of problem  solving is by a process
          known as  TOP-DOWN DESIGN. This involves  simplifying the problem
          into a series  of broad terms and then adding  more detail as you
          look at each  term. For example,  the solution to the  problem of
          how to update a bank account is given as follows:
               (a) Give a broad outline
                   1. read in transactions
                   2. bring the accounts up to date
                   3. print out a list of transactions
               (b) Start to refine the broad outline
                   1.1 read in credits
                   1.2 read in debits
                   2.1 calculate the total credits
                   2.2 calculate the total debits
                   2.3 calculate the new balance
                   3.1 print out credits
                   3.2 print out debits
                                                                         23
                   3.3 print out new balance
               Each  step would then  be further refined until  we are at a
          position to implement the solution.
               Implementing the  solution would result in  the construction
          of a Pascal program from our  refined design. When the program is
          entered  into the  computer  and successfully  executed then  the
          associated  problem  is  solved. Whenever  a  solution  is  to be
          implemented there are three important stages to consider :
               1) Data input or reading in information to the program.
               2) Processing instructions - the main body of the
                 program.
               3) Results or output. For example, to the printer or
                  screen or some other output device.
               After  having completed  the implementation  of  your design
          successfully,  always  go  back  over what  you  have  done,  ask
          yourself if you managed to solve the problem to your satisfaction
          and  does the  solution  work for  all  cases that  could  arise?
          Finally it  is vital that you  make a written record  of what you
          have  done. Say  for example  several months  later, you  want to
          amend  the  program. If  you  have  clear, concise  documentation
          covering all aspects of your design and solution then the process
          of amendment should be made much less painful.
               Now  lets take a simple problem and using the above, process
          solutions ready for implementation in Pascal.
          Problem 1 : We  are going back to  the bank again. This  time the
          bank manager has asked  us to devise  a program that will  assist
          his customers. He  wants an interactive program  that will prompt
          the user for the  balance in his deposit  account and then  print
          out, to the computer screen, the new balance with interest at the
          end of the first interest period.
          First lets ask some questions :
          1) What is the current rate of interest?
          2) Over what period is the interest calculated?
          3) How is the new balance to be calculated?
          4) What will happen if there is a withdrawal from the account?
               Ask  as many  questions  as you  like  until you  are  fully
          satisfied  that you  understand the  problem. For the  purpose of
          this problem  the bank  manager supplies  you with  the following
          answers :
          1) Interest is paid annually at 13%
          2) The period of interest is quarterly ie 3 months
          3) The new balance is the current deposit plus the accumulated
            interest.
                                                                         24
          4) The account is new and no withdrawals are allowed
               We are now in a position to construct a broad design of the
          intended program.
               1. set values of variables ( or initialize variables )
               2. calculate the new balance at the end of the quarter
               3. write out the result
               The broad  outline is  easy and understandable  and although
          this is a  much simplified  case the same  principles apply  when
          dealing with more complex design. Let us continue by refining the
          broad outline to a more specific list of instructions. 
               1.1 initialize interest rate
               1.2 initialize interest period
               1.3 initialize deposit
               2.1 calculate interest
               2.2 set balance to deposit + interest
               3.1 write out 'New balance is ', balance
               The design is now at  a stage where it could  be implemented
          as a Pascal program,  however there is nothing  to stop you  from
          further  refining the design if  you think it  necessary. At this
          point it would be beneficial to stop and  reflect over our design
          for a moment  or two. In  our design there  are three  statements
          asking  us to  initialize  certain  data,  if  a  data  table  is
          constructed we can  make clear  the data structures  and what  we
          intend to do with them.
          Identifier     Description                  Type
          ==========     ===========                  ====
          deposit        amount of money in account   real variable
          rate           interest rate per year       real variable
          interest       calculated interest          real variable
          quarter        period over which the
                         interest is calculated       real variable
          balance        total money in the account
                         after first quarter          real variable
               The design has introduced a new data  type REAL. A real data
          type is  defined as 'positive  and negative numbers  that include
          decimal points'. Our real variables must be initialized, or given
          a starting value before they  can appear in an expression  in the
          program.  It is  true  to  say  that  many  Pascal  systems  will
          automatically  initialize variables  to zero,  however it  is not
          good programming practice to presume this  and  it is much better
          if you ensure that all variables used in the program are properly
          initialized.
               Using  the data table we can now write the declarations part
          of the program ( the part where all the variables are declared ).
                                                                         25
          var
             deposit  : real;
             rate     : real;
             quarter  : real;
             interest : real;
             balance  : real;
          Note this could also be written as
          var
             deposit,rate,quarter,interest,balance : real;
          Initializing the variables in Pascal is as follows, using the
          statements from lines 1.1, 1.2 and 1.3 of our design
          rate := 0.13; { 13% as a decimal fraction }
          quarter := 0.25; { 1/4 of a year expressed as a decimal }
          readln(deposit);
               The  first  two  lines make  use  of  the Pascal  assignment
          operator ( := ) which we discussed in the last  issue of PNL. The
          readln statement  is new to us and merits a brief discussion. The
          use of the  readln statement  results in assigning  a value to  a
          variable  from an external source ie from outside the program. As
          our program specified an interactive design we expect the user to
          input some information, a passing of information must take  place
          between the user and  the computer. The program will  temporarily
          halt when it encounters the readln statement, expecting some data
          to be input.  It is vital  that interactive programs  communicate
          while they run  so that the user will know when he is required to
          input the data. The way to do this is to use a program prompt. It
          is  also important that interactive  prompts ask make clear their
          purpose. Lets add  an interactive prompt to  our program ensuring
          it comes before the readln statement.
          write('Please enter your deposit here > ');
          Steps 2.1 and 2.2 of our design are fairly straight forward
          and are implemented as follows :
          interest := deposit*rate*quarter;
          balance  := deposit + interest;
          Finally step1 requires the output to the screen for the
          information of the user.
          writeln('The new balance is ',balance:10:2);
               The writeln statement  informs the user of his  balance plus
          interest and provides a  neat format. The :10 after  the variable
          balance allows a field width of  10 characters for the output and
          the :2 ensures the  output is rounded to two decimal  places. Try
                                                                         26
          the program without these output controls and note the results.
          The whole program can now be put together;
          program new_balance;
          { calculates balance on deposit at a rate of 13 % over 3
          months }
          var
             deposit : real; { entered by user }
             rate    : real; { current rate of interest}
             quarter : real;  { a period of three months }
             interest : real; { calculated interest on deposit }
             balance  : real;  { the new balance }
          begin
             rate := 0.13;
             quarter := 0.25;
             write('Please enter your deposit here > ');
             readln(deposit);
             interest := deposit*rate*quarter;
             balance := deposit + interest;
             writeln('New balance is ',balance:10:2)
          end.
               In  conclusion top down design  is a method  for designing a
          solution  to  a problem  that  is going  to  be implemented  on a
          computer.  If you look at  the design, it  is understandable to a
          non  programming  person as  it conveys  a  broad outline  of the
          problem. With further levels  of refinement and a data  table you
          are ready to implement your design. Show your final coded program
          to the  same non programming  person and the chances  are that he
          will have no idea  what it is  about. By designing your  programs
          before coding them, you will have a much more clear understanding
          of exactly what your  program does and will find that coding soon
          becomes routine and effortless.
          Error  : In PNL # 3 the  following line should be inserted in the
          program add_up
          after male := 100; :- total := male + female;
          I hope that this  did not distract from the understanding  of the
          program.
                                                                         27
                              TechnoJock's Turbo Toolkit
                                       A Review
                                   by John A. Abele
               Those of us who are serious/professional programmers need to
          know about  those rare  individuals who have  become programmer's
          programmers. These  folks write the toolkits  and other utilities
          that save  the rest of us  from a lot of tedious  "grunt work" in
          our programming.
               TechnoJock's Turbo Toolkit is a well done series of 12 Turbo
          Pascal  units  that  is worthy  of  your  consideration.   It  is
          published by TechnoJock Software, Inc. of Houston, Texas.
               Briefly,  the 12  units  are as  follows:   1)  fast  screen
          updating,  2) windows  and  boxes,  3)  user input  training,  4)
          directory  display and  retrieval, 5)  keyboard  manipulation and
          reading,  6) dynamic list management (this one is a real winner),
          7) menu display,  8) nested  menuing, 9)  pull down  (Lotus-like)
          menuing,  10) data type reading (like  reading only integers from
          the keyboard), 11) string manipulating (some good functions found
          here),  and 12) things that don't fit  anywhere else such as date
          manipulating, file information, on-screen clock and some printer
          testers.
               You'll find  some razzle-dazzle procedures in  this toolkit,
          such as sliding part of a screen in from the left or windows that
          appear to explode from the center.
               What  I appreciate about this toolkit is its overall concern
          with what the  screen looks like.  We are all  aware that some of
          the best  programs never get used because the screen is sloppy or
          un-inviting to the user.  The programmer who uses this toolkit is
          constantly encouraged to make a nice looking screen.
               Definitely  "home-brewed",  these units  bear  the stamp  of
          experience:  one  programmer knowing  what another  programmer is
          likely to need.  This shows  itself in the intuitive names  given
          to  procedures and functions.  A procedure named "TempMessageBox"
          is fairly self-explanatory as to what it is likely to do.
               The Toolkit sells for $49.95  and includes full source  code
          to  all the units, a printed manual  and a diskette full of short
          demo programs showing how to use each of the units.
               I have, on several  occasions, written to Bob  Ainsbury, the
          so-called "janitor" of TechnoJock  Software, Inc., with questions
          or problems  about the  units.   In  EVERY CASE  I have  received
          either a personal letter explaining how to modify the source code
          to do what I want, or, a diskette with a fixed bug.  This support
          alone is well worth the price of registration. In addition, there
                                                                         28
          are  no royalty payments for using his units in compiled programs
          you  distribute  and  he  gives  his customers  price  breaks  on
          upgrades, etc.
               Are  there any minuses?  Yes, I've found two things that may
          stand  in  your  way  when  using this  toolkit.    One,  a minor
          complaint, is  that the manual  could be  cleaned up.   There are
          several  inconsistent references  to procedure  calls and  places
          where  some information is missing.  These errors are not serious
          enough to  prohibit your effectively  using the manual,  but they
          may  slow  you  down  at  times.    A  second  and  more  serious
          consideration is getting used  to how one uses all  the functions
          and  procedures in the toolkit.  Fortunately, the disk comes with
          an  ample supply of  programs which demo  how to  use the various
          units.   But be  prepared  to spend  some time  looking at  these
          programs  and  studying  the code  in  them.    Doing so  at  the
          beginning will save  frustration later.   You probably won't  hit
          the ground running with this toolkit.
               Beginner  and advanced  programmers  who are  looking for  a
          workhorse toolbox that  can do a lot and get  the job done should
          consider TechnoJock's Turbo Toolkit.
                                                                         29
                        Turbo Pascal - The Complete Reference
                      
                                       A Review
                                    By Pete Davis
               I picked this book as my first to review because I feel that
          this book is invaluable for any Turbo Pascal Programmer. The book
          would be useful for  everyone from beginner to the  veteran Turbo
          Pascal Programmer.  Stephen K.  O'Brien, the Author  is obviously
          very  comfortable with  Turbo  Pascal and  his  book is  official
          enough to have been published by Borland-Osbourne/McGraw-Hill. 
               My  copy is  actually  for Version  4 but  it  has been  re-
          released for Version 5.5 of Turbo Pascal, which for the most part
          is almost identical in  content with the addition of  coverage of
          some 5.5 features.
               The first seven chapters are devoted mostly to the beginner,
          covering  everything  from  basic  data  structures   to  program
          structure  to the  Turbo Pascal  environment. Chapter  eight then
          begins on more complex ideas like pointers and dynamic memory.
               The  explanations of ideas are  very clear and  to the point
          and the most useful feature is the volume of examples included in
          the text. Almost every idea covered includes examples which drive
          home the point of every section.
               My personal favorite is chapter eighteen : 'Optimizing Turbo
          Pascal Programs', in which the author provides incredible insight
          into how to  make you programs  run faster  and use less  memory.
          This  chapter alone nearly pays for the book, especially when you
          are involved in large projects where speed and code size are very
          important.
               Another nice feature is the Procedure and Function reference
          near the end  of the book. This and other  reference areas in the
          book  make the  Turbo Pascal  Reference Guide,  which comes  with
          Turbo Pascal, almost worthless. (I shouldn't  really say that, as
          the  reference  guide  is quite  useful,  but  many  of the  more
          important aspects are covered in The Complete Reference). 
               I give Stephen K.  O'Brien five stars for a  fantastic book.
          If you have been using Turbo  Pascal for a long time, or you  are
          just learning to use it, I suggest that  you grab a copy and read
          through it.  It is by  far the most  useful book on  Turbo Pascal
          that I have ever seen.
                                                   _Pete Davis
                                                    Editor
                                                                         30
                                      Conclusion
               Well,  I would  first like  to thank  everyone for  being so
          patient in waiting  for the release  of this issue of  the Pascal
          NewsLetter.  I apologize for the  delay, but there  were a lot of
          things that held this issue up. Some of them, many of you already
          know about. Between my vacation and some computer problems, there
          was about a two week delay.
               When  I was  just getting  ready to  start finishing  up the
          issue, I got a the Profiler article and program which  I just had
          to include in this issue. I  think you'll agree that it was worth
          the  wait. Getting the article translated to English was the last
          thing holding this issue up, but it was worth the wait for me. 
               I would  like  to thank  everyone  who contributed  to  this
          issue, in particular,  I would like to  thank Bob Gowans  for his
          continued support and work  in the For Beginners column.  I would
          like to  thank Jan-Erik Rosinowski  for a fantastic  program. His
          contributions  are  welcome here  anytime. I  would also  like to
          thank Howard Sanner  for putting in a lot  of time in translating
          the article for me. I couldn't have done it without him.
               I have a few surprises that I hope I can spring in  the next
          issue,  but I  can't be  sure of  whether or  not things  will go
          through by then. If not, I still plan on having a good issue out.
          Please  send  in  your  contributions. They're  what  keeps  this
          newsletter coming out.  Even if you don't write well, send in the
          best you  can do, and  if you're  uncomfortable with it,  I'll be
          happy to go through and edit it.
               Also,  please keep  the  comments  and  suggestions  coming.
          They're a great help.
               Until the next issue, happy computing.
                                                   _Pete Davis
           
                                                                         31
               The  Pascal  NewsLetter  is  Copyrighted  by    Pete  Davis.
          Articles  submitted by others are the property of the authors and
          are  used with  permission. They  may  not be  treated separately
          from  this newsletter  without the  author's permission  and thus
          maintain  all distribution rules of the newsletter as a whole. It
          may be  freely  distributed in  un-modified form,  but no  charge
          whatsoever may be incurred on the recipient. All code is provided
          'as-is' with no guarantees whatsoever.
               The  Pascal NewsLetter  can be  obtained from  the following
          locations:
                         GEnie : General Electric Network for Information  
                                 Exchange. It is located in the IBMPC 
                                 filelist.
                        Simtel : Internet address: 26.2.0.74 or            
                                 Simtel20.ARPA It is located in the        
                                 <MSDOS.PASCAL> directory.
            Programmer's Forum : This is the home of the PNL.              
                                 Each issue can be found in                
                                 the MAG directory from the main area.
                                 The number is on the title page.
               If   you  would like   to   receive  back   issues  of   PNL
          directly  from  me,  send  a  diskette  and  $2.00  for shipping.
          Don't forget to include your address.
                         Send your order to:
                            Peter Davis
                            4851 Brandywine St. NW
                            Washington, DC   20016
               If you  are   a SysOp that   will regularly  carry PNL   and
          would like to   have your  bulletin board listed  as such,  here,
          send  me  a message  either  by  postal mail  or  at  one of  the
          electronic  addresses  given   on  the  title    page, with  your
          bulletin board's name, phone number, and your name.
                                                                         32
                                  Distribution List
               The following  is the phone numbers to bulletin boards known
          to carry  PNL. If you would  like your bulletin board's  name and
          number added  to  or deleted  from this  list, please  send me  a
          message at one of my many addresses. I can not guarantee whether 
          a listed board will have any particular issue, however.
             The Programmer's Forum ................. Phone: (202) 966-3647
             The Programmer's Forum is the home of the PNL.
             The Bored .............................. Phone: (512) 303-0471
             Classic City ........................... Phone: (404) 548-0726
             Theive's World ......................... Phone: (713) 463-8053
             Hippocampus ............................ Phone: (203) 484-4621
             Rogers State College BBS ............... Phone: (918) 341-8982
             The Foxtrot BBS ........................ Phone: (914) 567-1814
             Turbo City BBS ......................... Phone: (209) 599-7435
             Austin IEMUG/MIDI BBS .................. Phone: (512) 528-0626
             Laser Publishers ....................... Phone: (918) 438-2749
             Fargo RBBS-PC .......................... Phone: (701) 293-5973
             Momentary Lapse of Reason .............. Phone: (704) 327-6361
             The Demon's Den ........................ Phone: (508) 433-2702
             The Cannibal Cafe ...................... Phone: (508) 840-6589
             IBM Tech FIDO .......................... Phone: (508) 433-6491
             The User Friendly BBS .................. Phone: (704) 323-8223
             The Disseminator BBS (Australia)........ Phone: 61-7-368-1239
             Beyound Frontiers BBS (Denmark)......... Phone: (+45)8694-1609
             Collision Theory ....................... Phone: (703) 503-9441
             Ed-Net 9600 ............................ Phone: (604) 732-8877
