home *** CD-ROM | disk | FTP | other *** search
/ Programmer 7500 / MAX_PROGRAMMERS.iso / INFO / TURBOPAS / TUTORPAS.ARC / TUTOR8.DOC < prev    next >
Encoding:
Text File  |  1989-05-21  |  26.4 KB  |  607 lines

  1. O
  2. PA A
  3.  
  4.  
  5.  
  6.  
  7.  
  8.  
  9.  
  10.  
  11.  
  12.  
  13.  
  14.  
  15.  
  16.  
  17.  
  18.  
  19.  
  20.  
  21.  
  22.  
  23.  
  24.  
  25.  
  26.  
  27.  
  28.  
  29.                             LET'S BUILD A COMPILER!
  30.  
  31.                                        By
  32.  
  33.                             Jack W. Crenshaw, Ph.D.
  34.  
  35.                                   2 April 1989
  36.  
  37.  
  38.                          Part VIII: A LITTLE PHILOSOPHY
  39.  
  40.  
  41.  
  42.  
  43.  
  44.  
  45.  
  46.  
  47.  
  48.  
  49.  
  50.  
  51.  
  52.  
  53.  
  54.  
  55.  
  56.  
  57.  
  58.  
  59.  
  60.  
  61.  
  62.  
  63.  
  64.  
  65.  
  66.  
  67.  
  68.  
  69. PA A
  70.  
  71.  
  72.  
  73.  
  74.  
  75.        *****************************************************************
  76.        *                                                               *
  77.        *                        COPYRIGHT NOTICE                       *
  78.        *                                                               *
  79.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  80.        *                                                               *
  81.        *****************************************************************
  82.  
  83.  
  84.        INTRODUCTION
  85.  
  86.        This is going to be a  different  kind of session than the others
  87.        in our series on  parsing  and  compiler  construction.  For this
  88.        session, there won't be  any  experiments to do or code to write.
  89.        This  once,  I'd  like  to  just  talk  with  you  for  a  while.
  90.        Mercifully, it will be a short  session,  and then we can take up
  91.        where we left off, hopefully with renewed vigor.
  92.  
  93.        When  I  was  in college, I found that I could  always  follow  a
  94.        prof's lecture a lot better if I knew where he was going with it.
  95.        I'll bet you were the same.
  96.  
  97.        So I thought maybe it's about  time  I told you where we're going
  98.        with this series: what's coming up in future installments, and in
  99.        general what all  this  is  about.   I'll also share some general
  100.        thoughts concerning the usefulness of what we've been doing.
  101.  
  102.  
  103.        THE ROAD HOME
  104.  
  105.        So far, we've  covered  the parsing and translation of arithmetic
  106.        expressions,  Boolean expressions, and combinations connected  by
  107.        relational  operators.    We've also done the  same  for  control
  108.        constructs.    In  all of this we've leaned heavily on the use of
  109.        top-down, recursive  descent  parsing,  BNF  definitions  of  the
  110.        syntax, and direct generation of assembly-language code.  We also
  111.        learned the value of  such  tricks  as single-character tokens to
  112.        help  us  see  the  forest  through  the  trees.    In  the  last
  113.        installment  we dealt with lexical scanning,  and  I  showed  you
  114.        simple but powerful ways to remove the single-character barriers.
  115.  
  116.        Throughout the whole study, I've emphasized  the  KISS philosophy
  117.        ... Keep It Simple, Sidney ... and I hope by now  you've realized
  118.        just  how  simple  this stuff can really be.  While there are for
  119.        sure areas of compiler  theory  that  are truly intimidating, the
  120.        ultimate message of this series is that in practice you  can just
  121.        politely  sidestep   many  of  these  areas.    If  the  language
  122.        definition  cooperates  or,  as in this series, if you can define
  123.        the language as you go, it's possible to write down  the language
  124.        definition in BNF with reasonable ease.  And, as we've  seen, you
  125.        can crank out parse procedures from the BNF just about as fast as
  126.        you can type.ABAB
  127.                                      - 2 -A*A*
  128.  
  129. PA A
  130.  
  131.  
  132.  
  133.  
  134.  
  135.        As our compiler has taken form, it's gotten more parts,  but each
  136.        part  is  quite small and simple, and  very  much  like  all  the
  137.        others.
  138.  
  139.        At this point, we have many  of  the makings of a real, practical
  140.        compiler.  As a matter of  fact,  we  already have all we need to
  141.        build a toy  compiler  for  a  language as powerful as, say, Tiny
  142.        BASIC.  In the next couple of installments, we'll  go  ahead  and
  143.        define that language.
  144.  
  145.        To round out  the  series,  we  still  have a few items to cover.
  146.        These include:
  147.  
  148.           o Procedure calls, with and without parameters
  149.  
  150.           o Local and global variables
  151.  
  152.           o Basic types, such as character and integer types
  153.  
  154.           o Arrays
  155.  
  156.           o Strings
  157.  
  158.           o User-defined types and structures
  159.  
  160.           o Tree-structured parsers and intermediate languages
  161.  
  162.           o Optimization
  163.  
  164.        These will all be  covered  in  future  installments.  When we're
  165.        finished, you'll have all the tools you need to design  and build
  166.        your own languages, and the compilers to translate them.
  167.  
  168.        I can't  design  those  languages  for  you,  but I can make some
  169.        comments  and  recommendations.    I've  already  sprinkled  some
  170.        throughout past installments.    You've  seen,  for  example, the
  171.        control constructs I prefer.
  172.  
  173.        These constructs are going  to  be part of the languages I build.
  174.        I  have  three  languages in mind at this point, two of which you
  175.        will see in installments to come:
  176.  
  177.        TINY - A  minimal,  but  usable  language  on the order  of  Tiny
  178.               BASIC or Tiny C.  It won't be very practical, but  it will
  179.               have enough power to let you write and  run  real programs
  180.               that do something worthwhile.
  181.  
  182.        KISS - The  language  I'm  building for my  own  use.    KISS  is
  183.               intended to be  a  systems programming language.  It won't
  184.               have strong typing  or  fancy data structures, but it will
  185.               support most of  the  things  I  want to do with a higher-
  186.               order language (HOL), except perhaps writing compilers.ABAB
  187.                                      - 3 -A*A*
  188.  
  189. PA A
  190.  
  191.  
  192.  
  193.  
  194.  
  195.        I've also  been  toying  for  years  with  the idea of a HOL-like
  196.        assembler,  with  structured  control  constructs   and  HOL-like
  197.        assignment statements.  That, in  fact, was the impetus behind my
  198.        original foray into the jungles of compiler theory.  This one may
  199.        never be built, simply  because  I've  learned that it's actually
  200.        easier to implement a language like KISS, that only uses a subset
  201.        of the CPU instructions.    As you know, assembly language can be
  202.        bizarre  and  irregular  in the extreme, and a language that maps
  203.        one-for-one onto it can be a real challenge.  Still,  I've always
  204.        felt that the syntax used  in conventional assemblers is dumb ...
  205.        why is
  206.  
  207.             MOVE.L A,B
  208.  
  209.        better, or easier to translate, than
  210.  
  211.             B=A ?
  212.  
  213.        I  think  it  would  be  an  interesting  exercise to  develop  a
  214.        "compiler" that  would give the programmer complete access to and
  215.        control over the full complement  of the CPU instruction set, and
  216.        would allow you to generate  programs  as  efficient  as assembly
  217.        language, without the pain  of  learning a set of mnemonics.  Can
  218.        it be done?  I don't  know.  The  real question may be, "Will the
  219.        resulting language be any  easier  to  write  than assembly"?  If
  220.        not, there's no point in it.  I think that it  can  be  done, but
  221.        I'm not completely sure yet how the syntax should look.
  222.  
  223.        Perhaps you have some  comments  or suggestions on this one.  I'd
  224.        love to hear them.
  225.  
  226.        You probably won't be surprised to learn that I've already worked
  227.        ahead in most  of the areas that we will cover.  I have some good
  228.        news:  Things  never  get  much  harder than they've been so far.
  229.        It's  possible  to  build a complete, working compiler for a real
  230.        language, using nothing  but  the same kinds of techniques you've
  231.        learned so far.  And THAT brings up some interesting questions.
  232.  
  233.  
  234.        WHY IS IT SO SIMPLE?
  235.  
  236.        Before embarking  on this series, I always thought that compilers
  237.        were just naturally complex computer  programs  ...  the ultimate
  238.        challenge.  Yet the things we have done here have  usually turned
  239.        out to be quite simple, sometimes even trivial.
  240.  
  241.        For awhile, I thought  is  was simply because I hadn't yet gotten
  242.        into the meat  of  the  subject.    I had only covered the simple
  243.        parts.  I will freely admit  to  you  that, even when I began the
  244.        series,  I  wasn't  sure how far we would be able  to  go  before
  245.        things got too complex to deal with in the ways  we  have so far.
  246.        But at this point I've already  been  down the road far enough to
  247.        see the end of it.  Guess what?A6A6
  248.                                      - 4 -A*A*
  249.  
  250. PA A
  251.  
  252.  
  253.  
  254.  
  255.  
  256.                             THERE ARE NO HARD PARTS!
  257.  
  258.  
  259.        Then, I thought maybe it was because we were not  generating very
  260.        good object  code.    Those  of  you  who have been following the
  261.        series and trying sample compiles know that, while the code works
  262.        and  is  rather  foolproof,  its  efficiency is pretty awful.   I
  263.        figured that if we were  concentrating on turning out tight code,
  264.        we would soon find all that missing complexity.
  265.  
  266.        To  some  extent,  that one is true.  In particular, my first few
  267.        efforts at trying to improve efficiency introduced  complexity at
  268.        an alarming rate.  But since then I've been tinkering around with
  269.        some simple optimizations and I've found some that result in very
  270.        respectable code quality, WITHOUT adding a lot of complexity.
  271.  
  272.        Finally, I thought that  perhaps  the  saving  grace was the "toy
  273.        compiler" nature of the study.   I  have made no pretense that we
  274.        were  ever  going  to be able to build a compiler to compete with
  275.        Borland and Microsoft.  And yet, again, as I get deeper into this
  276.        thing the differences are starting to fade away.
  277.  
  278.        Just  to make sure you get the message here, let me state it flat
  279.        out:
  280.  
  281.           USING THE TECHNIQUES WE'VE USED  HERE,  IT  IS  POSSIBLE TO
  282.           BUILD A PRODUCTION-QUALITY, WORKING COMPILER WITHOUT ADDING
  283.           A LOT OF COMPLEXITY TO WHAT WE'VE ALREADY DONE.
  284.  
  285.  
  286.        Since  the series began I've received  some  comments  from  you.
  287.        Most of them echo my own thoughts:  "This is easy!    Why  do the
  288.        textbooks make it seem so hard?"  Good question.
  289.  
  290.        Recently, I've gone back and looked at some of those texts again,
  291.        and even bought and read some new ones.  Each  time,  I come away
  292.        with the same feeling: These guys have made it seem too hard.
  293.  
  294.        What's going on here?  Why does the whole thing seem difficult in
  295.        the texts, but easy to us?    Are  we that much smarter than Aho,
  296.        Ullman, Brinch Hansen, and all the rest?
  297.  
  298.        Hardly.  But we  are  doing some things differently, and more and
  299.        more  I'm  starting  to appreciate the value of our approach, and
  300.        the way that  it  simplifies  things.    Aside  from  the obvious
  301.        shortcuts that I outlined in Part I, like single-character tokens
  302.        and console I/O, we have  made some implicit assumptions and done
  303.        some things differently from those who have designed compilers in
  304.        the past. As it turns out, our approach makes life a lot easier.
  305.  
  306.        So why didn't all those other guys use it?
  307.  
  308.        You have to remember the context of some of the  earlier compiler
  309.        development.  These people were working with very small computersA*A*
  310.                                      - 5 -
  311.  
  312. PA A
  313.  
  314.  
  315.  
  316.  
  317.  
  318.        of  limited  capacity.      Memory  was  very  limited,  the  CPU
  319.        instruction  set  was  minimal, and programs ran  in  batch  mode
  320.        rather  than  interactively.   As it turns out, these caused some
  321.        key design decisions that have  really  complicated  the designs.
  322.        Until recently,  I hadn't realized how much of classical compiler
  323.        design was driven by the available hardware.
  324.  
  325.        Even in cases where these  limitations  no  longer  apply, people
  326.        have  tended  to  structure their programs in the same way, since
  327.        that is the way they were taught to do it.
  328.  
  329.        In  our case, we have started with a blank sheet of paper.  There
  330.        is a danger there, of course,  that  you will end up falling into
  331.        traps that other people have long since learned to avoid.  But it
  332.        also has allowed us to  take different approaches that, partly by
  333.        design  and partly by pure dumb luck, have  allowed  us  to  gain
  334.        simplicity.
  335.  
  336.        Here are the areas that I think have  led  to  complexity  in the
  337.        past:
  338.  
  339.          o  Limited RAM Forcing Multiple Passes
  340.  
  341.             I  just  read  "Brinch  Hansen  on  Pascal   Compilers"  (an
  342.             excellent book, BTW).  He  developed a Pascal compiler for a
  343.             PC, but he started the effort in 1981 with a 64K system, and
  344.             so almost every design decision  he made was aimed at making
  345.             the compiler fit  into  RAM.    To do this, his compiler has
  346.             three passes, one of which is the lexical scanner.  There is
  347.             no way he could, for  example, use the distributed scanner I
  348.             introduced  in  the last installment,  because  the  program
  349.             structure wouldn't allow it.  He also required  not  one but
  350.             two intermediate  languages,  to  provide  the communication
  351.             between phases.
  352.  
  353.             All the early compiler writers  had to deal with this issue:
  354.             Break the compiler up into enough parts so that it  will fit
  355.             in memory.  When  you  have multiple passes, you need to add
  356.             data structures to support the  information  that  each pass
  357.             leaves behind for the next.   That adds complexity, and ends
  358.             up driving the  design.    Lee's  book,  "The  Anatomy  of a
  359.             Compiler,"  mentions a FORTRAN compiler developed for an IBM
  360.             1401.  It had no fewer than 63 separate passes!  Needless to
  361.             say,  in a compiler like this  the  separation  into  phases
  362.             would dominate the design.
  363.  
  364.             Even in  situations  where  RAM  is  plentiful,  people have
  365.             tended  to  use  the same techniques because  that  is  what
  366.             they're familiar with.   It  wasn't  until Turbo Pascal came
  367.             along that we found how simple a compiler could  be  if  you
  368.             started with different assumptions.
  369.  
  370.  
  371.          o  Batch ProcessingA*A*
  372.                                      - 6 -
  373.  
  374. PA A
  375.  
  376.  
  377.  
  378.  
  379.  
  380.             In the early days, batch  processing was the only choice ...
  381.             there was no interactive computing.   Even  today, compilers
  382.             run in essentially batch mode.
  383.  
  384.             In a mainframe compiler as  well  as  many  micro compilers,
  385.             considerable effort is expended on error recovery ... it can
  386.             consume as much as 30-40%  of  the  compiler  and completely
  387.             drive the design.  The idea is to avoid halting on the first
  388.             error, but rather to keep going at all costs,  so  that  you
  389.             can  tell  the  programmer about as many errors in the whole
  390.             program as possible.
  391.  
  392.             All of that harks back to the days of the  early mainframes,
  393.             where turnaround time was measured  in hours or days, and it
  394.             was important to squeeze every last ounce of information out
  395.             of each run.
  396.  
  397.             In this series, I've been very careful to avoid the issue of
  398.             error recovery, and instead our compiler  simply  halts with
  399.             an error message on  the  first error.  I will frankly admit
  400.             that it was mostly because I wanted to take the easy way out
  401.             and keep things simple.   But  this  approach,  pioneered by
  402.             Borland in Turbo Pascal, also has a lot going for it anyway.
  403.             Aside from keeping the  compiler  simple,  it also fits very
  404.             well  with   the  idea  of  an  interactive  system.    When
  405.             compilation is  fast, and especially when you have an editor
  406.             such as Borland's that  will  take you right to the point of
  407.             the error, then it makes a  lot  of sense to stop there, and
  408.             just restart the compilation after the error is fixed.
  409.  
  410.  
  411.          o  Large Programs
  412.  
  413.             Early compilers were designed to handle  large  programs ...
  414.             essentially infinite ones.    In those days there was little
  415.             choice;  the  idea  of  subroutine  libraries  and  separate
  416.             compilation  were  still  in  the  future.      Again,  this
  417.             assumption led to  multi-pass designs and intermediate files
  418.             to hold the results of partial processing.
  419.  
  420.             Brinch Hansen's  stated goal was that the compiler should be
  421.             able to compile itself.   Again, because of his limited RAM,
  422.             this drove him to a multi-pass design.  He needed  as little
  423.             resident compiler code as possible,  so  that  the necessary
  424.             tables and other data structures would fit into RAM.
  425.  
  426.             I haven't stated this one yet, because there  hasn't  been a
  427.             need  ... we've always just read and  written  the  data  as
  428.             streams, anyway.  But  for  the  record,  my plan has always
  429.             been that, in  a  production compiler, the source and object
  430.             data should all coexist  in  RAM with the compiler, a la the
  431.             early Turbo Pascals.  That's why I've been  careful  to keep
  432.             routines like GetChar  and  Emit  as  separate  routines, inA6A6
  433.                                      - 7 -A*A*
  434.  
  435. PA A
  436.  
  437.  
  438.  
  439.  
  440.  
  441.             spite of their small size.   It  will be easy to change them
  442.             to read to and write from memory.
  443.  
  444.  
  445.          o  Emphasis on Efficiency
  446.  
  447.             John  Backus has stated that, when  he  and  his  colleagues
  448.             developed the original FORTRAN compiler, they KNEW that they
  449.             had to make it produce tight code.  In those days, there was
  450.             a strong sentiment against HOLs  and  in  favor  of assembly
  451.             language, and  efficiency was the reason.  If FORTRAN didn't
  452.             produce very good  code  by  assembly  standards,  the users
  453.             would simply refuse to use it.  For the record, that FORTRAN
  454.             compiler turned out to  be  one  of  the most efficient ever
  455.             built, in terms of code quality.  But it WAS complex!
  456.  
  457.             Today,  we have CPU power and RAM size  to  spare,  so  code
  458.             efficiency is not  so  much  of  an  issue.    By studiously
  459.             ignoring this issue, we  have  indeed  been  able to Keep It
  460.             Simple.    Ironically,  though, as I have said, I have found
  461.             some optimizations that we can  add  to  the  basic compiler
  462.             structure, without having to add a lot of complexity.  So in
  463.             this  case we get to have our cake and eat it too:  we  will
  464.             end up with reasonable code quality, anyway.
  465.  
  466.  
  467.          o  Limited Instruction Sets
  468.  
  469.             The early computers had primitive instruction sets.   Things
  470.             that  we  take  for granted, such as  stack  operations  and
  471.             indirect addressing, came only with great difficulty.
  472.  
  473.             Example: In most compiler designs, there is a data structure
  474.             called the literal pool.  The compiler  typically identifies
  475.             all literals used in the program, and collects  them  into a
  476.             single data structure.    All references to the literals are
  477.             done  indirectly  to  this  pool.    At  the   end   of  the
  478.             compilation, the  compiler  issues  commands  to  set  aside
  479.             storage and initialize the literal pool.
  480.  
  481.             We haven't had to address that  issue  at all.  When we want
  482.             to load a literal, we just do it, in line, as in
  483.  
  484.                  MOVE #3,D0
  485.  
  486.             There is something to be said for the use of a literal pool,
  487.             particularly on a machine like  the 8086 where data and code
  488.             can  be separated.  Still, the whole  thing  adds  a  fairly
  489.             large amount of complexity with little in return.
  490.  
  491.             Of course, without the stack we would be lost.  In  a micro,
  492.             both  subroutine calls and temporary storage depend  heavily
  493.             on the stack, and  we  have used it even more than necessary
  494.             to ease expression parsing.A*A*
  495.                                      - 8 -
  496.  
  497. PA A
  498.  
  499.  
  500.  
  501.  
  502.  
  503.          o  Desire for Generality
  504.  
  505.             Much of the content of the typical compiler text is taken up
  506.             with issues we haven't addressed here at all ... things like
  507.             automated  translation  of  grammars,  or generation of LALR
  508.             parse tables.  This is not simply because  the  authors want
  509.             to impress you.  There are good, practical  reasons  why the
  510.             subjects are there.
  511.  
  512.             We have been concentrating on the use of a recursive-descent
  513.             parser to parse a  deterministic  grammar,  i.e.,  a grammar
  514.             that is not ambiguous and, therefore, can be parsed with one
  515.             level of lookahead.  I haven't made much of this limitation,
  516.             but  the  fact  is  that  this represents a small subset  of
  517.             possible grammars.  In fact,  there is an infinite number of
  518.             grammars that we can't parse using our techniques.    The LR
  519.             technique is a more powerful one, and can deal with grammars
  520.             that we can't.
  521.  
  522.             In compiler theory, it's important  to know how to deal with
  523.             these  other  grammars,  and  how  to  transform  them  into
  524.             grammars  that  are  easier to deal with.  For example, many
  525.             (but not all) ambiguous  grammars  can  be  transformed into
  526.             unambiguous ones.  The way to do this is not always obvious,
  527.             though, and so many people  have  devoted  years  to develop
  528.             ways to transform them automatically.
  529.  
  530.             In practice, these  issues  turn out to be considerably less
  531.             important.  Modern languages tend  to be designed to be easy
  532.             to parse, anyway.   That  was a key motivation in the design
  533.             of Pascal.   Sure,  there are pathological grammars that you
  534.             would be hard pressed to write unambiguous BNF  for,  but in
  535.             the  real  world  the best answer is probably to avoid those
  536.             grammars!
  537.  
  538.             In  our  case,  of course, we have sneakily let the language
  539.             evolve  as  we  go, so we haven't painted ourselves into any
  540.             corners here.  You may not always have that luxury.   Still,
  541.             with a little  care  you  should  be able to keep the parser
  542.             simple without having to resort to automatic  translation of
  543.             the grammar.
  544.  
  545.  
  546.        We have taken  a  vastly  different  approach in this series.  We
  547.        started with a clean sheet  of  paper,  and  developed techniques
  548.        that work in the context that  we  are in; that is, a single-user
  549.        PC  with  rather  ample CPU power and RAM space.  We have limited
  550.        ourselves to reasonable grammars that  are easy to parse, we have
  551.        used the instruction set of the CPU to advantage, and we have not
  552.        concerned ourselves with efficiency.  THAT's why it's been easy.
  553.  
  554.        Does this mean that we are forever doomed  to  be  able  to build
  555.        only toy compilers?   No, I don't think so.  As I've said, we can
  556.        add  certain   optimizations   without   changing   the  compilerA*A*
  557.                                      - 9 -
  558.  
  559. PA A
  560.  
  561.  
  562.  
  563.  
  564.  
  565.        structure.  If we want to process large files, we can  always add
  566.        file  buffering  to do that.  These  things  do  not  affect  the
  567.        overall program design.
  568.  
  569.        And I think  that's  a  key  factor.   By starting with small and
  570.        limited  cases,  we  have been able to concentrate on a structure
  571.        for  the  compiler  that is natural  for  the  job.    Since  the
  572.        structure naturally fits the job, it is almost bound to be simple
  573.        and transparent.   Adding  capability doesn't have to change that
  574.        basic  structure.    We  can  simply expand things like the  file
  575.        structure or add an optimization layer.  I guess  my  feeling  is
  576.        that, back when resources were tight, the structures people ended
  577.        up  with  were  artificially warped to make them work under those
  578.        conditions, and weren't optimum  structures  for  the  problem at
  579.        hand.
  580.  
  581.  
  582.        CONCLUSION
  583.  
  584.        Anyway, that's my arm-waving  guess  as to how we've been able to
  585.        keep things simple.  We started with something simple and  let it
  586.        evolve  naturally,  without  trying  to   force   it   into  some
  587.        traditional mold.
  588.  
  589.        We're going to  press on with this.  I've given you a list of the
  590.        areas  we'll  be  covering in future installments.    With  those
  591.        installments, you  should  be  able  to  build  complete, working
  592.        compilers for just about any occasion, and build them simply.  If
  593.        you REALLY want to build production-quality compilers,  you'll be
  594.        able to do that, too.
  595.  
  596.        For those of you who are chafing at the bit for more parser code,
  597.        I apologize for this digression.  I just thought  you'd  like  to
  598.        have things put  into  perspective  a  bit.  Next time, we'll get
  599.        back to the mainstream of the tutorial.
  600.  
  601.        So far, we've only looked at pieces of compilers,  and  while  we
  602.        have  many  of  the  makings  of a complete language, we  haven't
  603.        talked about how to put  it  all  together.    That  will  be the
  604.        subject of our next  two  installments.  Then we'll press on into
  605.        the new subjects I listed at the beginning of this installment.
  606.  
  607.        See you then.
  608.  
  609.        *****************************************************************
  610.        *                                                               *
  611.        *                        COPYRIGHT NOTICE                       *
  612.        *                                                               *
  613.        *   Copyright (C) 1989 Jack W. Crenshaw. All rights reserved.   *
  614.        *                                                               *
  615.        *****************************************************************ANAN
  616.                                     - 10 -A*A*
  617. @