home *** CD-ROM | disk | FTP | other *** search
/ Programmer's ROM - The Computer Language Library / programmersrom.iso / ada / misc / filecomp.ada < prev    next >
Encoding:
Text File  |  1988-05-03  |  91.6 KB  |  2,588 lines

  1. -------- SIMTEL20 Ada Software Repository Prologue ------------
  2. --                                                           -*
  3. -- Unit name    : generic package File_Compare_Utilities
  4. -- Version      : 2.0 (SUEP207)
  5. -- Author       : Geoffrey O. Mendal
  6. --              : Stanford University
  7. --              : Computer Systems Laboratory
  8. --              : Stanford, CA  94305
  9. --              : (415) 723-1414 or 723-1175
  10. -- DDN Address  : Mendal@Sierra.Stanford.Arpa
  11. -- Copyright    : (c) 1985, 1986, 1987 Geoffrey O. Mendal
  12. -- Date created : Sat 28 Dec 85
  13. -- Release date : Sun 29 Dec 85
  14. -- Last update  : MENDAL Sun 20 Sep 87
  15. -- Machine/System Compiled/Run on : DG MV10000, ROLM ADE
  16. --                                  VAX 11/780, DEC ACS
  17. --                                  RATIONAL R1000
  18. --                                  Sequent DYNIX, VADS
  19. --                                  Sun/3 UNIX, VADS
  20. -- Dependent Units : package TEXT_IO
  21. --                   package CALENDAR
  22. --                   package TOD_Utilities
  23. --                                                           -*
  24. ---------------------------------------------------------------
  25. --                                                           -*
  26. -- Keywords     :  COMPARE
  27. ----------------:  FILE COMPARE
  28. --
  29. -- Abstract     :  This generic package contains routines to
  30. ----------------:  compare two ASCII files.  It produces as
  31. ----------------:  output a side-by-side listing of both files,
  32. ----------------:  showing their differences in a very readable
  33. ----------------:  format, and also produces an update deck which
  34. ----------------:  can be used to provide a mapping between the
  35. ----------------:  two files.  This update deck is meant to be
  36. ----------------:  input for a revision control package, called
  37. ----------------:  Context_Directed_Update_Utilities.
  38. --                                                           -*
  39. ------------------ Revision history ---------------------------
  40. --                                                           -*
  41. -- DATE         VERSION              AUTHOR     HISTORY
  42. -- 12/29/85     1.0 (SAEC285)     Mendal     Initial Release
  43. -- 01/24/86     1.1 (FRAN246)     Mendal     Bug fixes, enhancements
  44. -- 04/19/86     1.2 (SAPR196)     Mendal     Enhancements
  45. -- 09/20/87     2.0 (SUEP207)     Mendal     Major interface enhancements
  46. --                                                           -*
  47. ------------------ Distribution and Copyright -----------------
  48. --                                                           -*
  49. -- This prologue must be included in all copies of this software.
  50. --
  51. -- This software is copyright by the author.
  52. --
  53. -- This software is released to the Ada community.
  54. -- This software is released to the Public Domain (note:
  55. --   software released to the Public Domain is not subject
  56. --   to copyright protection).
  57. -- Restrictions on use or distribution:  NONE
  58. --                                                           -*
  59. ------------------ Disclaimer ---------------------------------
  60. --                                                           -*
  61. -- This software and its documentation are provided "AS IS" and
  62. -- without any expressed or implied warranties whatsoever.
  63. -- No warranties as to performance, merchantability, or fitness
  64. -- for a particular purpose exist.
  65. --
  66. -- Because of the diversity of conditions and hardware under
  67. -- which this software may be used, no warranty of fitness for
  68. -- a particular purpose is offered.  The user is advised to
  69. -- test the software thoroughly before relying on it.  The user
  70. -- must assume the entire risk and liability of using this
  71. -- software.
  72. --
  73. -- In no event shall any person or organization of people be
  74. -- held responsible for any direct, indirect, consequential
  75. -- or inconsequential damages or lost profits.
  76. --                                                           -*
  77. -------------------END-PROLOGUE--------------------------------
  78.  
  79. -- File_Compare_Utilities is an ASCII file comparison package.  It
  80. -- takes two files as input and produces a list file and context
  81. -- directed update deck as output.  Several options are available
  82. -- which control what output is to be produced and how the files
  83. -- should be compared.  The context directed update deck provides
  84. -- a mapping from the "old" file to the "new" one, and can be used
  85. -- as input to the Context_Directed_Update_Utilities package to
  86. -- derive the "new" file given the "old" file and the CDUPDATE deck.
  87. -- Hence, this package can be used as a means of revision control.
  88.  
  89. -- The package body performs its own garbage collection, which increases
  90. -- the speed of the algorithm.  Doing so, however, requires that the
  91. -- package maintain a global free list.  Hence, use of this package
  92. -- in concurrent environments is discouraged.  The package reads in
  93. -- a maximum number of lines for each file.  This number is controlled
  94. -- by means of a lookahead value which constrains the algorithm in
  95. -- finding synchronization points in both files.  One may notice on the
  96. -- side-by-side listings and CDUPDATE decks that a maximum number of
  97. -- lines in a group (equal, insertion, deletion) is less than that
  98. -- of the true number in a group.  This is due to the constrained
  99. -- lookahead value.  A user may alter this lookahead value, but
  100. -- is strongly discouraged from doing so (see below for details).
  101.  
  102. -- This package has been formally annotated using the ANNA specification
  103. -- language.  For more information, contact the author at the above
  104. -- address.
  105.  
  106. with TEXT_IO;  -- Predefined I/O package.
  107.  
  108. generic
  109.   Maximum_File_Name_Length : in     POSITIVE := 100;
  110.   Maximum_Line_Length      : in     POSITIVE := 256;
  111.  
  112.   -- The above values are used to specify the maximum length
  113.   -- of strings.  Such strings are used in the package body.
  114.  
  115.   with procedure Get_A_Line (
  116.                    File   : in     TEXT_IO.FILE_TYPE;
  117.                    Line   :    out STRING;
  118.                    Length :    out NATURAL) is TEXT_IO.GET_LINE;
  119.   -- | where TEXT_IO.IS_OPEN (File),
  120.   -- |       TEXT_IO.MODE (File) = TEXT_IO.OUT_FILE,
  121.   -- |       raise TEXT_IO.END_ERROR => FALSE,
  122.   -- |       Line'FIRST = 1 and Line'LAST = Maxmimum_Line_Length,
  123.   -- |       out (0 <= Length <= Maxmimum_Line_Length)
  124.   -- |       out (for all I : NATURAL range 1 .. Length =>
  125.   -- |         Line (I)'DEFINED);
  126.   -- Anna doesn't allow subprogram annotations on generic formals, hence
  127.   -- the reason for "-- |" instead of "--|".
  128.  
  129.   -- The above subprogram allows a user to override the line
  130.   -- entry method of TEXT_IO.GET_LINE and instead write a routine
  131.   -- that returns a string resulting from a user-defined "line".
  132.   -- For all user-defined actual subprograms, the bounds of
  133.   -- the returned "Line" string value must be exactly that of
  134.   -- (1 .. Maximum_Line_Length), else CONSTRAINT_ERROR will be
  135.   -- propagated to this package, and this package will then
  136.   -- propagate Line_Length_Error to the caller.  A user should
  137.   -- return the true last character of "Line" by means of the
  138.   -- "Length" parameter, that is, it will be assumed that
  139.   -- Line (1 .. Length) contains the line to be compared.
  140. package File_Compare_Utilities is
  141.   function Version return STRING;  -- Returns the version number.
  142.  
  143.   -- The following type can be used to specify the case of CDUPDATE
  144.   -- deck commands in the CDUPDATE deck file.
  145.  
  146.   type Type_Set is (Upper_Case, Lower_Case, Mixed_Case);
  147.  
  148.   -- The following type can be used to retrieve statistics generated
  149.   -- by the Compare subprograms.
  150.  
  151.   type Statistics_Type is
  152.     record
  153.       Files_Equal         : BOOLEAN := FALSE;  -- TRUE if files are equal, FALSE otherwise
  154.       Number_Old_Lines,                    -- Number of lines in the old file
  155.       Number_New_Lines,                    -- Number of lines in the new file
  156.       Total_Equal_Lines,                   -- Number of equal lines found
  157.       Total_Insertions,                    -- Number of insertions found
  158.       Total_Deletions,                     -- Number of deletions found
  159.       Total_Minor_Changes : NATURAL := 0;  -- Number of minor changes found
  160.     end record;
  161.  
  162.   -- The following type can be used to specify options to the Compare
  163.   -- subprograms.  Note that a default options record is provided below.
  164.  
  165.   type Options_Type is
  166.     record
  167.       Produce_Listing,                         -- Print a side-by-side listing
  168.                                                -- of both files
  169.       Summarize,                               -- Summarizes equal lines,
  170.                                                -- insertions, and deletions
  171.                                                -- in the listing (groups them)
  172.       Wide_Listing,                            -- Line printer style listing
  173.       Produce_Deck,                            -- Generate a CDUPDATE deck
  174.       Verbose_Deck,                            -- Spell out everything in full
  175.       Produce_Statistics,                      -- Generate statistics
  176.       Check_Minor_Changes,                     -- Check for minor changes in lines
  177.       Case_Sensitive       : BOOLEAN := TRUE;  -- Distinguish between upper and
  178.                                                -- lower case
  179.       Deck_Command_Case    : Type_Set := Mixed_Case;  -- Case of deck commands
  180.       Lookahead            : POSITIVE := 500;  -- Synchronization point constraint
  181.       Minimum_Group        : POSITIVE range 3 .. POSITIVE'LAST := 3;
  182.                                        -- Minimum number of lines on which
  183.     end record;                        -- Summarize has an effect
  184.  
  185.   Default_Options : Options_Type;
  186.  
  187.   -- Notes on the Options:
  188.   --   (1) Summarize will cause just the first and last lines in a
  189.   --       group of equal lines, insertions, or deletions to be printed.
  190.   --       In addition, a special notation will be made on the listing
  191.   --       stating the total number of lines in the group.  This is
  192.   --       useful for summarizing the differences in the files.
  193.   --       Otherwise, each and every line is printed in full on the
  194.   --       listing.  The user can control how many lines determine
  195.   --       a "group", and hence has some control in producing summarized
  196.   --       output.  (See the Minimum_Group option for details.)
  197.   --   (2) Wide_Listing will cause the list file to print correctly
  198.   --       for a line printer (132 column printer).  If this option
  199.   --       is set to FALSE, the listing will print correctly for
  200.   --       a terminal screen (80 column screen) or printer that
  201.   --       can only print a maximum of 80 columns per line.
  202.   --   (3) Verbose_Deck causes Delete commands to appear
  203.   --       explicitly in the CDUPDATE deck.  Such commands are not
  204.   --       really required.  The Context Directed Update utility will
  205.   --       delete any lines not explicitly accounted for in a
  206.   --       CDUPDATE deck.  Hence, Delete commands are an aid for the
  207.   --       programmer.  This option can be set to FALSE so as to save
  208.   --       space (albeit not much) in the CDUPDATE deck.  With this
  209.   --       option set to FALSE, commands are also abbreviated to
  210.   --       their fullest, "noise word" parameters are eliminated,
  211.   --       some extra spacing is condensed between parameters,
  212.   --       and no comment commands are produced.
  213.   --   (4) Check_Minor_Changes causes the algorithm to check each
  214.   --       line for minor changes.  By setting this value to FALSE,
  215.   --       the speed of the algorithm can be increased at the expense
  216.   --       of a more brute force comparison approach.
  217.   --   (5) Case_Sensitive causes lines to be compared with regard
  218.   --       for upper and lower case.  If a case insensitive comparison
  219.   --       is desired, this option should be set to FALSE.  If this
  220.   --       option is set to FALSE and a minor change is found, an
  221.   --       Edit command in the CDUPDATE deck will still be generated
  222.   --       with case sensitive parameters.
  223.   --   (6) If the Produce_Listing and/or Produce_Deck options are set
  224.   --       to FALSE, a user need not provide their file names to the
  225.   --       Compare subprograms.  Instead, a user can provide null
  226.   --       values.  (The strings/files will be ignored in such cases anyway.)
  227.   --   (7) If the Produce_Statistics option is set to FALSE, then
  228.   --       the result returned by the Compare subprograms is
  229.   --       undeterminate; do not rely on the values of this record
  230.   --       in such cases.
  231.   --   (8) The Lookahead value provided to the Compare subprograms
  232.   --       establishes the maximum number of lines to read in for
  233.   --       EACH file.  A larger lookahead will of course make finding
  234.   --       synchronization points easier, but will also consume more
  235.   --       memory.  Hence, users are cautioned in altering this
  236.   --       Lookahead value.  It is possible that STORAGE_ERROR will
  237.   --       be propagated to the caller if too large a Lookahead is
  238.   --       provided.  Providing a smaller Lookahead may solve this
  239.   --       problem.
  240.   --   (9) The Minimum_Group value determines the minimum number
  241.   --       of lines in a group (insertions, deletions, or equal lines)
  242.   --       on which the Summarize option will have an effect.  This
  243.   --       option only has an effect of Summarize is set to TRUE.
  244.  
  245.   Line_Length_Error,
  246.   File_Name_Length_Error,
  247.   Old_File_Open_Error,
  248.   New_File_Open_Error,
  249.   List_File_Create_Error,
  250.   Deck_File_Create_Error  : exception;
  251.  
  252.   -- Notes on the exceptions:
  253.   --   (1) The Line_Length_Error exception is propagated
  254.   --       when a user-defined Get_A_Line subprogram returns a
  255.   --       Line string whose bounds are not exactly that specified
  256.   --       by (1 .. Maximum_Line_Length).
  257.   --   (2) The "Open_Error" exceptions are propagated when the
  258.   --       subprograms perform TEXT_IO.OPEN on the old and
  259.   --       new files as TEXT_IO.IN_FILE but a TEXT_IO exception
  260.   --       was raised.  The most probable error is that the file
  261.   --       doesn't exist or access to it is not allowed.  These exceptions
  262.   --       are also propagated if the file arguments do not designate
  263.   --       legal files.
  264.   --   (3) The "Create_Error" exceptions are propagated when the
  265.   --       subprograms perform TEXT_IO.CREATE on the list and deck
  266.   --       files as TEXT_IO.OUT_FILE but a TEXT_IO exception was
  267.   --       raised.  These exceptions are also propagated if the file
  268.   --       arguments do not designate legal files.
  269.   --   (4) Once the files have been successfully opened and created,
  270.   --       no more exception trapping is performed.  If a TEXT_IO
  271.   --       operation fails, the TEXT_IO exception will be propagated
  272.   --       immediately to the caller.
  273.   --   (5) The File_Name_Length_Error exception is propagated when a
  274.   --       subprogram is called, and the length of any file name
  275.   --       is greater than that of Maximum_File_Name_Length.
  276.  
  277.   -- The following characters are used as codes in the listing
  278.   -- and CDUPDATE deck files.  The subtype allows for only non-
  279.   -- blank printable characters.
  280.  
  281.   subtype Code_Character_Type is CHARACTER range '!' .. '~';
  282.  
  283.   Equal_Lines_Code  : Code_Character_Type := '=';
  284.   Minor_Change_Code : Code_Character_Type := '*';
  285.   Insertion_Code    : Code_Character_Type := '+';
  286.   Deletion_Code     : Code_Character_Type := '-';
  287.   Command_Code      : Code_Character_Type := '/';
  288.  
  289.   function "=" (L, R : in TEXT_IO.FILE_MODE) return BOOLEAN
  290.     renames TEXT_IO."=";
  291.  
  292.   --: function Can_Open_File (F : in STRING) return BOOLEAN;
  293.   --: function Can_Create_File (F : in STRING) return BOOLEAN;
  294.  
  295.   -- The following subprogram takes an old, new, list, and deck file
  296.   -- as input, and (depending on the options set) returns
  297.   -- statistics, a side-by-side listing, and a CDUPDATE deck.
  298.   -- The old and new files must already be opened for input, and the list and
  299.   -- deck files must already be opened for output.  No files will be closed.
  300.  
  301.   procedure Compare (
  302.     Old_File,
  303.     New_File,
  304.     List_File,
  305.     Deck_File  : in     TEXT_IO.FILE_TYPE;
  306.     Statistics :    out Statistics_Type;
  307.     Options    : in     Options_Type := Default_Options);
  308.   --| where not TEXT_IO.IS_OPEN (Old_File) or else
  309.   --|         TEXT_IO.MODE (Old_File) /= TEXT_IO.IN_FILE =>
  310.   --|         raise Old_File_Open_Error,
  311.   --|       not TEXT_IO.IS_OPEN (New_File) or else
  312.   --|         TEXT_IO.MODE (New_File) /= TEXT_IO.IN_FILE =>
  313.   --|         raise New_File_Open_Error,
  314.   --|       not TEXT_IO.IS_OPEN (List_File) or else
  315.   --|         TEXT_IO.MODE (List_File) /= TEXT_IO.OUT_FILE =>
  316.   --|         raise List_File_Create_Error,
  317.   --|       not TEXT_IO.IS_OPEN (Deck_File) or else
  318.   --|         TEXT_IO.MODE (Deck_File) /= TEXT_IO.OUT_FILE =>
  319.   --|         raise Deck_File_Create_Error,
  320.   --|       raise Old_File_Open_Error | New_File_Open_Error |
  321.   --|         List_File_Create_Error | Deck_File_Create_Error =>
  322.   --|         Statistics = in Statistics,
  323.   --|       raise File_Name_Length_Error => FALSE,
  324.   --|       out (Statistics'DEFINED and
  325.   --|         TEXT_IO.END_OF_FILE (Old_File) and
  326.   --|         TEXT_IO.END_OF_FILE (New_File) and
  327.   --|         TEXT_IO.IS_OPEN (List_File) and
  328.   --|         TEXT_IO.IS_OPEN (Deck_File));
  329.  
  330.   -- The following overloading should be used when no statistics
  331.   -- are required.  Note that if the Produce_Statistics and
  332.   -- Produce_Listing options are both set to TRUE, statistics
  333.   -- will still be printed on the listing.
  334.  
  335.   procedure Compare (
  336.     Old_File,
  337.     New_File,
  338.     List_File,
  339.     Deck_File  : in     TEXT_IO.FILE_TYPE;
  340.     Options    : in     Options_Type := Default_Options);
  341.   --| where not TEXT_IO.IS_OPEN (Old_File) or else
  342.   --|         TEXT_IO.MODE (Old_File) /= TEXT_IO.IN_FILE =>
  343.   --|         raise Old_File_Open_Error,
  344.   --|       not TEXT_IO.IS_OPEN (New_File) or else
  345.   --|         TEXT_IO.MODE (New_File) /= TEXT_IO.IN_FILE =>
  346.   --|         raise New_File_Open_Error,
  347.   --|       not TEXT_IO.IS_OPEN (List_File) or else
  348.   --|         TEXT_IO.MODE (List_File) /= TEXT_IO.OUT_FILE =>
  349.   --|         raise List_File_Create_Error,
  350.   --|       not TEXT_IO.IS_OPEN (Deck_File) or else
  351.   --|         TEXT_IO.MODE (Deck_File) /= TEXT_IO.OUT_FILE =>
  352.   --|         raise Deck_File_Create_Error,
  353.   --|       raise File_Name_Length_Error => FALSE,
  354.   --|       out (TEXT_IO.END_OF_FILE (Old_File) and
  355.   --|         TEXT_IO.END_OF_FILE (New_File) and
  356.   --|         TEXT_IO.IS_OPEN (List_File) and
  357.   --|         TEXT_IO.IS_OPEN (Deck_File));
  358.  
  359.   -- The following subprogram performs only a quick comparison
  360.   -- of the old and new files.  Only a boolean result is returned.
  361.   -- TRUE is returned if the files are equal, otherwise FALSE
  362.   -- is returned.  This subprogram uses a different and more
  363.   -- efficient algorithm in comparing the files, since it does not
  364.   -- have to generate a side-by-side listing nor a CDUPDATE deck.
  365.   -- The user should not depend on the state of the files upon return.
  366.   -- The algorithm does not always read to the end of file for both files.
  367.  
  368.   function Quick_Compare (
  369.     Old_File,
  370.     New_File       : in     TEXT_IO.FILE_TYPE;
  371.     Case_Sensitive : in     BOOLEAN := TRUE) return BOOLEAN;
  372.   --| where not TEXT_IO.IS_OPEN (Old_File) or else
  373.   --|         TEXT_IO.MODE (Old_File) /= TEXT_IO.IN_FILE =>
  374.   --|         raise Old_File_Open_Error,
  375.   --|       not TEXT_IO.IS_OPEN (New_File) or else
  376.   --|         TEXT_IO.MODE (New_File) /= TEXT_IO.IN_FILE =>
  377.   --|         raise New_File_Open_Error,
  378.   --|       raise File_Name_Length_Error => FALSE;
  379.   ----------------------------------------------------------------------------
  380.  
  381.   -- The following three subprograms behave similar to the above three,
  382.   -- except that the file names are passed as strings.
  383.  
  384.   -- The following subprogram takes an old, new, list, and deck file
  385.   -- name as input, and (depending on the options set), returns
  386.   -- statistics, a side-by-side listing, and a CDUPDATE deck.
  387.   -- The old and new file names will be opened, and the list and
  388.   -- deck files will be created (if the list and/or deck files already
  389.   -- exist, they will be overwritten).  All files will be closed
  390.   -- upon normal termination.
  391.  
  392.   procedure Compare (
  393.     Old_File_Name,
  394.     New_File_Name,
  395.     List_File_Name,
  396.     Deck_File_Name  : in     STRING;
  397.     Statistics      :    out Statistics_Type;
  398.     Options         : in     Options_Type := Default_Options);
  399.   --| where Old_File_Name > Maximum_File_Length or
  400.   --|         New_File_Name > Maximum_File_Length or
  401.   --|         List_File_Name > Maximum_File_Length or
  402.   --|         Deck_File_Name > Maximum_File_Length =>
  403.   --|         raise File_Name_Length_Error,
  404.   --|       not Can_Open_File (Old_File_Name) =>
  405.   --|         raise Old_File_Open_Error,
  406.   --|       not Can_Open_File (New_File_Name) =>
  407.   --|         raise New_File_Open_Error,
  408.   --|       not Can_Create_File (List_File_Name) and
  409.   --|           Options.Produce_Listing =>
  410.   --|         raise List_File_Create_Error,
  411.   --|       not Can_Create_File (Deck_File_Name) and
  412.   --|           Options.Produce_Deck =>
  413.   --|         raise Deck_File_Create_Error,
  414.   --|       raise File_Name_Length_Error | Old_File_Open_Error |
  415.   --|         New_File_Open_Error | List_File_Create_Error |
  416.   --|         Deck_File_Create_Error => Statistics = in Statistics,
  417.   --|       out Statistics'DEFINED;
  418.  
  419.   -- The following overloading should be used when no statistics
  420.   -- are required.  Note that if the Produce_Statistics and
  421.   -- Produce_Listing options are both set to TRUE, statistics
  422.   -- will still be printed on the listing.
  423.  
  424.   procedure Compare (
  425.     Old_File_Name,
  426.     New_File_Name,
  427.     List_File_Name,
  428.     Deck_File_Name  : in     STRING;
  429.     Options         : in     Options_Type := Default_Options);
  430.   --| where Old_File_Name > Maximum_File_Length or
  431.   --|         New_File_Name > Maximum_File_Length or
  432.   --|         List_File_Name > Maximum_File_Length or
  433.   --|         Deck_File_Name > Maximum_File_Length =>
  434.   --|         raise File_Name_Length_Error,
  435.   --|       not Can_Open_File (Old_File_Name) =>
  436.   --|         raise Old_File_Open_Error,
  437.   --|       not Can_Open_File (New_File_Name) =>
  438.   --|         raise New_File_Open_Error,
  439.   --|       not Can_Create_File (List_File_Name) and
  440.   --|           Options.Produce_Listing =>
  441.   --|         raise List_File_Create_Error,
  442.   --|       not Can_Create_File (Deck_File_Name) and
  443.   --|           Options.Produce_Deck =>
  444.   --|         raise Deck_File_Create_Error;
  445.  
  446.   -- The following subprogram performs only a quick comparison
  447.   -- of the old and new files.  Only a boolean result is returned.
  448.   -- TRUE is returned if the files are equal, otherwise FALSE
  449.   -- is returned.  This subprogram uses a different and more
  450.   -- efficient algorithm in comparing the files, since it does not
  451.   -- have to generate a side-by-side listing nor a CDUPDATE deck.
  452.  
  453.   function Quick_Compare (
  454.     Old_File_Name,
  455.     New_File_Name  : in     STRING;
  456.     Case_Sensitive : in     BOOLEAN := TRUE) return BOOLEAN;
  457.   --| where not Can_Open_File (Old_File_Name) =>
  458.   --|         raise Old_File_Open_Error,
  459.   --|       not Can_Open_File (New_File_Name) =>
  460.   --|         raise New_File_Open_Error,
  461.   --|       raise File_Name_Length_Error => FALSE;
  462. end File_Compare_Utilities;
  463. ------------------------------------------------------------------------
  464. -- Example uses:
  465.  
  466. -- Example #1: Compare two files for equality
  467. -- with File_Compare_Utilities, TEXT_IO;
  468. -- procedure Main is
  469. --   package Compare_Utilities is new File_Compare_Utilities;
  470. -- begin
  471. --   TEXT_IO.PUT ("Files F1 and F2 are ");
  472. --
  473. --   if not Compare_Utilities.Quick_Compare ("F1", "F2") then
  474. --     TEXT_IO.PUT ("not ");
  475. --   end if;
  476. --
  477. --   TEXT_IO.PUT_LINE ("equal.");
  478. -- end Main;
  479. -- ---------------------------------------------------------------------
  480. -- Example #2: Compare two files and generate all possible output
  481. -- with File_Compare_Utilities;
  482. -- procedure Main is
  483. --   Statistics : File_Compare_Utilities.Statistics_Type;
  484. --   package Compare_Utilities is new File_Compare_Utilities;
  485. -- begin
  486. --   Compare_Utilities.Compare ("Main.Bak", "Main.Ada",
  487. --     "Listing", "Cdupdate_Deck", Statistics);
  488. -- end Main;
  489. -- ---------------------------------------------------------------------
  490. -- Example #3: Compare two files, alter the maximum line length,
  491. --             and modify the character code objects, and options.
  492. -- with File_Compare_Utilities;
  493. -- procedure Main is
  494. --   package Compare_Utilities is new File_Compare_Utilities (
  495. --                                      Maximum_Line_Length => 80);
  496. -- begin
  497. --   Compare_Utilities.Equal_Lines_Code := 'E';
  498. --   Compare_Utilities.Command_Code := '#';
  499. --   Compare_Utilities.Default_Options.Produce_Deck := FALSE;
  500. --   Compare_Utilities.Default_Options.Wide_Listing := FALSE;
  501. --   Compare_Utilities.Default_Options.Lookahead := 50;
  502. --   Compare_Utilities.Compare ("F1", "F2", "L", "");
  503. -- end Main;
  504. -- ---------------------------------------------------------------------
  505. -- Example #4: Compare two files, using a user-defined Get_A_Line
  506. --             subprogram.  File objects are passed in.
  507. -- with File_Compare_Utilities, TEXT_IO;
  508. -- procedure Main is
  509. --   New_File, Old_File, List_File, Deck_File : TEXT_IO.FILE_TYPE;
  510. --   procedure My_Get_Line (
  511. --     F : in     TEXT_IO.FILE_TYPE;
  512. --     S :    out STRING;
  513. --     N :    out NATURAL);
  514. --   package Compare_Utilities is new File_Compare_Utilities (
  515. --                                      Get_A_Line => My_Get_Line);
  516. --   procedure My_Get_Line (
  517. --     F : in     TEXT_IO.FILE_TYPE;
  518. --     S :    out STRING;
  519. --     N :    out NATURAL) is
  520. --     Str : STRING (1 .. 500) := (others => ASCII.NUL);
  521. --     Len : NATURAL;
  522. --   begin
  523. --     -- read in a line, stripping off the first five characters
  524. --     TEXT_IO.GET_LINE (F, Str, Len);
  525. --     Str (1 .. Len - 5) := Str (6 .. Len);
  526. --     S := Str (1 .. 256);
  527. --     if Len > 261 then
  528. --       N := 256;
  529. --     else
  530. --       N := Len - 5;
  531. --     end if;
  532. --   end My_Get_Line;
  533. -- begin
  534. --   TEXT_IO.OPEN (Old_File, TEXT_IO.IN_FILE, "F1");
  535. --   TEXT_IO.OPEN (New_File, TEXT_IO.IN_FILE, "F2");
  536. --   TEXT_IO.CREATE (List_File, TEXT_IO.OUT_FILE, "L");
  537. --   TEXT_IO.CREATE (Deck_File, TEXT_IO.OUT_FILE, "D");
  538. --
  539. --   Compare_Utilities.Compare (Old_File, New_File, List_File, Deck_File);
  540. --
  541. --   TEXT_IO.CLOSE (Old_File);
  542. --   TEXT_IO.CLOSE (New_File);
  543. --   TEXT_IO.CLOSE (List_File);
  544. --   TEXT_IO.CLOSE (Deck_File);
  545. -- end Main;
  546.  
  547. with CALENDAR,       -- predefined time of day package
  548.      TOD_Utilities;  -- GOM time of day utility package
  549.  
  550. package body File_Compare_Utilities is
  551.   -- Global constants used throughout the package body follow.
  552.   -- They eliminate magic numbers and frequently used character
  553.   -- literals, making the code more readable and reliable.
  554.  
  555.   List_Line_Num_Max_Length  : constant POSITIVE  :=   4;
  556.   Small_Margin              : constant POSITIVE  :=  31;
  557.   Large_Margin              : constant POSITIVE  :=  57;
  558.   Squote                    : constant CHARACTER := ''';
  559.   Dquote                    : constant CHARACTER := '"';
  560.   Blank                     : constant CHARACTER := ' ';
  561.   Version_Number            : constant STRING    := "2.0 (SUEP207)";
  562.   Authors_List              : constant STRING    :=
  563.     "Geoff Mendal, Stanford University (CSL)";
  564.   Uc_Lc_Offset              : constant POSITIVE  :=
  565.     CHARACTER'POS (ASCII.LC_A) - CHARACTER'POS ('A');
  566.  
  567.   subtype Set_of_Upper_Case_Letters is CHARACTER range 'A' .. 'Z';
  568.   subtype Set_of_Lower_Case_Letters is CHARACTER range
  569.     ASCII.LC_A .. ASCII.LC_Z;
  570.  
  571.   subtype File_Name_Type is STRING (1 .. Maximum_File_Name_Length);
  572.  
  573.   type Files_Type is  -- a composite of information on files used
  574.     record
  575.       Old_File_Name,
  576.       New_File_Name,
  577.       List_File_Name,
  578.       Deck_File_Name    : File_Name_Type := (others => Blank);
  579.       Old_File_Length,
  580.       New_File_Length,
  581.       List_File_Length,
  582.       Deck_File_Length  : NATURAL := 0;
  583.     end record;
  584.  
  585.   subtype Line_Type is STRING (1 .. Maximum_Line_Length);  -- a line of data
  586.  
  587.   type Data_Line_Type is  -- a composite of a line of data and its length
  588.     record
  589.       Line   : Line_Type;
  590.       Length : NATURAL;
  591.     end record;
  592.  
  593.   type Data_Type;
  594.  
  595.   type Data_Ptr_Type is access Data_Type;
  596.  
  597.   type Data_Type is  -- an element of a linked list of lines in a file
  598.     record
  599.       Data_Line   : Data_Line_Type;
  600.       Line_Number : NATURAL;
  601.       Next_Line   : Data_Ptr_Type;
  602.     end record;
  603.  
  604.   type Dispose_Record_Type is  -- a composite of garbage collection info
  605.     record
  606.       Old_File,
  607.       New_File  : NATURAL;
  608.     end record;
  609.  
  610.   type Minor_Change_Found_Type is (No_Minor_Change_Found,
  611.     Insertion_Found, Deletion_Found, Replacement_Found,
  612.     Transposition_Found);
  613.  
  614.   Free_List_Head,
  615.   Free_List_Tail  : Data_Ptr_Type := null;  -- global free list pointers
  616.   Old_Delimiter,
  617.   New_Delimiter   : CHARACTER;  -- used for parameters on /EDIT commands
  618.   Text_Max_Length : POSITIVE;  -- used for wide/compressed list file output
  619.  
  620.   package Int_IO is new TEXT_IO.INTEGER_IO (NATURAL);
  621.  
  622.   --: function Can_Open_File (F : in STRING) return BOOLEAN is
  623.   --:   File : TEXT_IO.FILE_TYPE;
  624.   --: begin
  625.   --:   TEXT_IO.OPEN (File, TEXT_IO.IN_FILE, F);
  626.   --:   TEXT_IO.CLOSE (File);
  627.   --:   return TRUE;
  628.   --: exception
  629.   --:   when others =>
  630.   --:     return FALSE;
  631.   --: end Can_Open_File;
  632.  
  633.   --: function Can_Create_File (F : in STRING) return BOOLEAN is
  634.   --:   File : TEXT_IO.FILE_TYPE;
  635.   --: begin
  636.   --:   TEXT_IO.CREATE (File, TEXT_IO.OUT_FILE, F);
  637.   --:   TEXT_IO.DELETE (File);
  638.   --:   return TRUE;
  639.   --: exception
  640.   --:   when others =>
  641.   --:     return FALSE;
  642.   --: end Can_Create_File;
  643.  
  644.   function Version return STRING is
  645.   begin
  646.     return Version_Number;
  647.   end Version;
  648.  
  649.   -- The following function converts a string into either
  650.   -- upper case, lower case, or mixed case.  It assumes that
  651.   -- strings are passed into it in mixed case.
  652.  
  653.   function Case_Conversion (
  654.     Str        : in STRING;
  655.     Convert_To : in Type_Set) return STRING is
  656.  
  657.     Str_Copy : STRING (Str'RANGE) := Str;
  658.   begin
  659.     case Convert_To is
  660.       when Mixed_Case => null;
  661.       when Upper_Case =>
  662.         for I in Str'RANGE loop
  663.           if Str (I) in Set_of_Lower_Case_Letters then
  664.             Str_Copy (I) := CHARACTER'VAL (CHARACTER'POS (Str (I)) - Uc_Lc_Offset);
  665.           end if;
  666.         end loop;
  667.       when Lower_Case =>
  668.         for I in Str'RANGE loop
  669.           if Str (I) in Set_of_Upper_Case_Letters then
  670.             Str_Copy (I) := CHARACTER'VAL (CHARACTER'POS (Str (I)) + Uc_Lc_Offset);
  671.           end if;
  672.         end loop;
  673.     end case;
  674.  
  675.     return Str_Copy;
  676.   end Case_Conversion;
  677.   ----------------------------------------------------------------------
  678.  
  679.   -- The following procedure outputs header info to the list and
  680.   -- deck files.
  681.  
  682.   procedure Headings (
  683.     Options        : in     Options_Type;
  684.     List_File,
  685.     Deck_File      : in     TEXT_IO.FILE_TYPE;
  686.     Files          : in out Files_Type) is
  687.  
  688.     TOD : STRING (1 ..
  689.       TOD_Utilities.External_TOD_Representation_Type'LAST + 3) :=
  690.       (others => Blank);
  691.     TOD_Length : POSITIVE;
  692.  
  693.     -- The following inner procedure is used to truncate file
  694.     -- names which are too long to fit on the output files.
  695.     -- Such truncation is only used for output purposes and
  696.     -- has no side effects.  Note that truncation is taken from
  697.     -- the beginning of the string, not the end as is normal.
  698.  
  699.     procedure Set_Printable_File_Length (
  700.       File_Name   : in out File_Name_Type;
  701.       File_Length : in out NATURAL) is
  702.     begin
  703.       for I in File_Name'RANGE loop
  704.         if File_Name (I) not in Code_Character_Type then
  705.           File_Name (I) := Blank;
  706.         end if;
  707.       end loop;
  708.  
  709.       if File_Length > Text_Max_Length then
  710.         File_Name (File_Name'FIRST .. File_Name'FIRST + Text_Max_Length - 1) :=
  711.           "..." & File_Name (File_Name'FIRST + File_Length - Text_Max_Length + 3 ..
  712.                              File_Name'FIRST + File_Length - 1);
  713.         File_Length := Text_Max_Length;
  714.       end if;
  715.     end Set_Printable_File_Length;
  716.  
  717.     -- The following inner procedure removes extra blanks in
  718.     -- the time-of-day string returned by GOM's time-of-day
  719.     -- conversion utility.
  720.  
  721.     procedure Compress (
  722.       Str               : in out STRING;
  723.       Compressed_Length :    out POSITIVE) is
  724.  
  725.       Str_Copy : STRING (Str'RANGE) := (others => Blank);
  726.       Str_Ptr,
  727.       Str_Ptr_Copy : POSITIVE := Str'FIRST;
  728.     begin
  729.       while (Str_Ptr <= Str'LAST) and then
  730.             (Str (Str_Ptr) = Blank) loop
  731.         Str_Ptr := Str_Ptr + 1;
  732.       end loop;
  733.  
  734.       while (Str_Ptr <= Str'LAST - 2) loop
  735.         if    (Str (Str_Ptr)     = Blank) and
  736.               (Str (Str_Ptr + 1) = Blank) and
  737.               (Str (Str_Ptr + 2) = Blank) then
  738.           Str_Ptr := Str_Ptr + 2;
  739.         elsif (Str (Str_Ptr)     = Blank) and
  740.               (Str (Str_Ptr + 1) = Blank) then
  741.           Str_Ptr      := Str_Ptr + 2;
  742.           Str_Ptr_Copy := Str_Ptr_Copy + 1;
  743.         elsif (Str (Str_Ptr)     = Blank) then
  744.           Str_Ptr      := Str_Ptr + 1;
  745.           Str_Ptr_Copy := Str_Ptr_Copy + 1;
  746.         else
  747.           Str_Copy (Str_Ptr_Copy) := Str (Str_Ptr);
  748.           Str_Ptr := Str_Ptr + 1;
  749.           Str_Ptr_Copy := Str_Ptr_Copy + 1;
  750.         end if;
  751.       end loop;
  752.  
  753.       if (Str'FIRST + Str'LAST - 1 >= 3) and then
  754.          ((Str (Str'LAST - 2) /= Blank) and
  755.           (Str (Str'LAST - 1) =  Blank) and
  756.           (Str (Str'LAST)     /= Blank)) then
  757.         Str_Ptr_Copy := Str_Ptr_Copy + 1;
  758.       end if;
  759.  
  760.       if (Str'FIRST + Str'LAST - 1) >= 2 and then
  761.          (Str (Str'LAST - 1) /= Blank) then
  762.         Str_Copy (Str_Ptr_Copy) := Str (Str'LAST - 1);
  763.         Str_Ptr_Copy := Str_Ptr_Copy + 1;
  764.       end if;
  765.  
  766.       if (Str'FIRST + Str'LAST - 1 >= 1) and then
  767.          (Str (Str'FIRST) /= Blank) then
  768.         Str_Copy (Str_Ptr_Copy) := Str (Str'LAST);
  769.         Str_Ptr_Copy := Str_Ptr_Copy + 1;
  770.       end if;
  771.  
  772.       Str := Str_Copy;
  773.       Compressed_Length := Str_Ptr_Copy - Str'FIRST;
  774.     end Compress;
  775.   begin  -- Headings
  776.     Set_Printable_File_Length (Files.Old_File_Name,
  777.       Files.Old_File_Length);
  778.     Set_Printable_File_Length (Files.New_File_Name,
  779.       Files.New_File_Length);
  780.     Set_Printable_File_Length (Files.List_File_Name,
  781.       Files.List_File_Length);
  782.     Set_Printable_File_Length (Files.Deck_File_Name,
  783.       Files.Deck_File_Length);
  784.  
  785.     TOD (1 .. TOD_Utilities.External_TOD_Representation_Type'LAST) :=
  786.       TOD_Utilities.Convert_Internal_TOD_to_External_TOD (
  787.         CALENDAR.CLOCK, TOD_Utilities.Mixed_Case);
  788.     TOD (29 .. TOD'LAST) := "at " & TOD (29 .. 38);
  789.     Compress (TOD, TOD_Length);
  790.     TOD (TOD_Length - 1) := CHARACTER'VAL (
  791.       CHARACTER'POS (TOD (TOD_Length - 1)) + Uc_Lc_Offset);
  792.  
  793.     if Options.Produce_Deck then
  794.       if not Options.Verbose_Deck then
  795.         TEXT_IO.PUT_LINE (Deck_File, Command_Code &
  796.           Case_Conversion ("B", Options.Deck_Command_Case));
  797.       else
  798.         TEXT_IO.PUT_LINE (Deck_File, Command_Code &
  799.           Case_Conversion ("Begin", Options.Deck_Command_Case));
  800.         TEXT_IO.PUT_LINE (Deck_File, Command_Code & "-- CDUPDATE deck " &
  801.           "generated by FILE COMPARE on " & TOD (1 .. TOD_Length));
  802.         TEXT_IO.PUT_LINE (Deck_File, Command_Code & "-- FILE COMPARE -- " &
  803.           "Version " & Version);
  804.         TEXT_IO.PUT_LINE (Deck_File, Command_Code & "-- Written by " &
  805.           Authors_List);
  806.         TEXT_IO.PUT_LINE (Deck_File, Command_Code & "-- This deck " &
  807.           "provides a mapping from " &
  808.           Files.Old_File_Name (1 .. Files.Old_File_Length) & " to " &
  809.           Files.New_File_Name (1 .. Files.New_File_Length));
  810.         TEXT_IO.PUT (Deck_File, Command_Code &
  811.           "-- This comparison is being performed with");
  812.  
  813.         if not Options.Case_Sensitive then
  814.           TEXT_IO.PUT (Deck_File, "out");
  815.         end if;
  816.  
  817.         TEXT_IO.PUT_LINE (Deck_File, " respect for case sensitivity");
  818.       end if;
  819.     end if;
  820.  
  821.     if Options.Produce_Listing then
  822.       if Options.Wide_Listing then
  823.         TEXT_IO.PUT (List_File, "                          ");
  824.       end if;
  825.  
  826.       TEXT_IO.PUT_LINE (List_File,
  827.         "            F I L E   C O M P A R E   P R O G R A M   " &
  828.         "L I S T I N G");
  829.       TEXT_IO.NEW_LINE (List_File);
  830.       TEXT_IO.PUT_LINE (List_File, "FILE COMPARE -- Version " &
  831.         Version);
  832.       TEXT_IO.PUT_LINE (List_File, "Written by " & Authors_List);
  833.       TEXT_IO.NEW_LINE (List_File);
  834.       TEXT_IO.PUT_LINE (List_File, "Comparison generated on " &
  835.         TOD (1 .. TOD_Length));
  836.       TEXT_IO.NEW_LINE (List_File);
  837.       TEXT_IO.PUT (List_File, "This comparison is being performed with");
  838.  
  839.       if not Options.Case_Sensitive then
  840.         TEXT_IO.PUT (List_File, "out");
  841.       end if;
  842.  
  843.       TEXT_IO.PUT_LINE (List_File, " respect for case sensitivity");
  844.       TEXT_IO.NEW_LINE (List_File, 2);
  845.  
  846.       TEXT_IO.PUT (List_File, "C Line " &
  847.         Files.Old_File_Name (1 .. Files.Old_File_Length));
  848.  
  849.       for I in 1 .. (Text_Max_Length - Files.Old_File_Length) loop
  850.         TEXT_IO.PUT (List_File, Blank);
  851.       end loop;
  852.  
  853.       TEXT_IO.PUT (List_File, " | " &
  854.         Files.New_File_Name (1 .. Files.New_File_Length));
  855.  
  856.       for I in 1 .. (Text_Max_Length - Files.New_File_Length) loop
  857.         TEXT_IO.PUT (List_File, Blank);
  858.       end loop;
  859.  
  860.       TEXT_IO.PUT_LINE (List_File, " Line C");
  861.  
  862.       if Options.Wide_Listing then
  863.         TEXT_IO.PUT_LINE (List_File,
  864.           "-----------------------------------------------------------------+" &
  865.           "-----------------------------------------------------------------");
  866.       else
  867.         TEXT_IO.PUT_LINE (List_File,
  868.           "---------------------------------------+" &
  869.           "---------------------------------------");
  870.       end if;
  871.     end if;
  872.   end Headings;
  873.   ----------------------------------------------------------------------
  874.  
  875.   -- The following procedure is the hub of all input operations.
  876.   -- It makes use of this package's own garbage collection too.
  877.  
  878.   procedure Read_File (
  879.     Max_Lines_to_Read : in     NATURAL;
  880.     A_File            : in     TEXT_IO.FILE_TYPE;
  881.     Line_Number       : in out POSITIVE;
  882.     File_Head_Ptr     : in out Data_Ptr_Type) is
  883.  
  884.     Curr_Ptr,
  885.     Tail_Ptr     : Data_Ptr_Type := File_Head_Ptr;
  886.     Number_Lines : NATURAL := 1;
  887.   begin
  888.     -- Position the tail pointer at the end of the linked list
  889.     -- for the file.
  890.  
  891.     if Tail_Ptr /= null then
  892.       while Tail_Ptr.Next_Line /= null loop
  893.         Tail_Ptr := Tail_Ptr.Next_Line;
  894.       end loop;
  895.     end if;
  896.  
  897.     -- Read in data
  898.  
  899.     while (not TEXT_IO.END_OF_FILE (A_File)) and
  900.           (Number_Lines <= Max_Lines_to_Read) loop
  901.       if Free_List_Head = null then
  902.         Curr_Ptr := new Data_Type;
  903.       else
  904.         Curr_Ptr := Free_List_Head;
  905.         Free_List_Head := Free_List_Head.Next_Line;
  906.       end if;
  907.  
  908.       begin
  909.         Get_A_Line (A_File, Curr_Ptr.Data_Line.Line,
  910.           Curr_Ptr.Data_Line.Length);
  911.       exception
  912.         when CONSTRAINT_ERROR =>
  913.           raise Line_Length_Error;
  914.       end;
  915.  
  916.       -- Blank the remainder of the string, even though it
  917.       -- is never referenced.
  918.  
  919.       for I in Curr_Ptr.Data_Line.Length + 1 ..
  920.                Curr_Ptr.Data_Line.Line'LAST loop
  921.         Curr_Ptr.Data_Line.Line (I) := Blank;
  922.       end loop;
  923.  
  924.       Number_Lines := Number_Lines + 1;
  925.  
  926.       -- Hook up the line in the linked list
  927.  
  928.       if Tail_Ptr = null then
  929.         File_Head_Ptr := Curr_Ptr;
  930.       else
  931.         Tail_Ptr.Next_Line := Curr_Ptr;
  932.       end if;
  933.  
  934.       Curr_Ptr.Line_Number := Line_Number;
  935.       Line_Number := Line_Number + 1;
  936.       Tail_Ptr := Curr_Ptr;
  937.       Curr_Ptr.Next_Line := null;
  938.     end loop;
  939.  
  940.     if Free_List_Head = null then
  941.       Free_List_Tail := null;
  942.     end if;
  943.   end Read_File;
  944.   ----------------------------------------------------------------------
  945.  
  946.   -- The following procedure prints lines on the list file.  The first
  947.   -- and last lines are passed, and this procedure iterates over all
  948.   -- lines from first to last inclusive.
  949.  
  950.   procedure Print_Listing (
  951.     Change_Code : in     CHARACTER;
  952.     First_Old,
  953.     Last_Old,
  954.     First_New,
  955.     Last_New    : in     Data_Ptr_Type;
  956.     List_File   : in     TEXT_IO.FILE_TYPE) is
  957.  
  958.     Curr            : POSITIVE;
  959.     Bool1,
  960.     Bool2           : BOOLEAN;
  961.     First_Old_Copy  : Data_Ptr_Type := First_Old;
  962.     First_New_Copy  : Data_Ptr_Type := First_New;
  963.   begin
  964.     loop
  965.       if First_Old_Copy = null then
  966.         TEXT_IO.PUT (List_File, Change_Code & "      ");
  967.       else
  968.         TEXT_IO.PUT (List_File, Change_Code & Blank);
  969.         Int_IO.PUT (List_File, First_Old_Copy.Line_Number,
  970.           List_Line_Num_Max_Length);
  971.         TEXT_IO.PUT (List_File, Blank);
  972.       end if;
  973.  
  974.       Curr := 1;
  975.  
  976.       loop
  977.         if First_Old_Copy = null then
  978.           for I in 1 .. Text_Max_Length loop
  979.             TEXT_IO.PUT (List_File, Blank);
  980.           end loop;
  981.         else
  982.           for I in 1 .. Text_Max_Length loop
  983.             if Curr + I - 1 > First_Old_Copy.Data_Line.Length then
  984.               TEXT_IO.PUT (List_File, Blank);
  985.             else
  986.               TEXT_IO.PUT (List_File,
  987.                 First_Old_Copy.Data_Line.Line (Curr+I-1));
  988.             end if;
  989.           end loop;
  990.         end if;
  991.  
  992.         TEXT_IO.PUT (List_File, " | ");
  993.  
  994.         if First_New_Copy /= null then
  995.           for I in 1 .. Text_Max_Length loop
  996.             if Curr + I - 1 > First_New_Copy.Data_Line.Length then
  997.               TEXT_IO.PUT (List_File, Blank);
  998.             else
  999.               TEXT_IO.PUT (List_File,
  1000.                 First_New_Copy.Data_Line.Line (Curr+I-1));
  1001.             end if;
  1002.           end loop;
  1003.         end if;
  1004.  
  1005.         Curr := Curr + Text_Max_Length;
  1006.  
  1007.         if First_Old_Copy = null then
  1008.           Bool1 := FALSE;
  1009.         else
  1010.           Bool1 := (Curr <= First_Old_Copy.Data_Line.Length);
  1011.         end if;
  1012.  
  1013.         if First_New_Copy = null then
  1014.           Bool2 := FALSE;
  1015.         else
  1016.           Bool2 := (Curr <= First_New_Copy.Data_Line.Length);
  1017.         end if;
  1018.  
  1019.         if Bool1 or Bool2 then
  1020.           TEXT_IO.NEW_LINE (List_File);
  1021.           TEXT_IO.PUT (List_File, "       ");
  1022.         end if;
  1023.  
  1024.         if First_Old_Copy = null then
  1025.           Bool1 := TRUE;
  1026.         else
  1027.           Bool1 := (Curr > First_Old_Copy.Data_Line.Length);
  1028.         end if;
  1029.  
  1030.         if First_New_Copy = null then
  1031.           Bool2 := TRUE;
  1032.         else
  1033.           Bool2 := (Curr > First_New_Copy.Data_Line.Length);
  1034.         end if;
  1035.  
  1036.         exit when Bool1 and Bool2;
  1037.       end loop;
  1038.  
  1039.       if First_New_Copy = null then
  1040.         for I in 1 .. Text_Max_Length loop
  1041.           TEXT_IO.PUT (List_File, Blank);
  1042.         end loop;
  1043.  
  1044.         TEXT_IO.PUT_LINE (List_File, "      " & Change_Code);
  1045.       else
  1046.         TEXT_IO.PUT (List_File, Blank);
  1047.         Int_IO.PUT (List_File, First_New_Copy.Line_Number,
  1048.           List_Line_Num_Max_Length);
  1049.         TEXT_IO.PUT_LINE (List_File, Blank & Change_Code);
  1050.       end if;
  1051.  
  1052.       exit when (First_Old_Copy = Last_Old) and
  1053.                 (First_New_Copy = Last_New);
  1054.  
  1055.       if First_Old_Copy /= Last_Old then
  1056.         First_Old_Copy := First_Old_Copy.Next_Line;
  1057.       end if;
  1058.  
  1059.       if First_New_Copy /= Last_New then
  1060.         First_New_Copy := First_New_Copy.Next_Line;
  1061.       end if;
  1062.     end loop;
  1063.   end Print_Listing;
  1064.   ----------------------------------------------------------------------
  1065.  
  1066.   -- The following procedure converts lines of data to upper case.
  1067.  
  1068.   procedure Convert_to_Upper_Case (Line : in out Data_Line_Type) is
  1069.   begin
  1070.     for I in 1 .. Line.Length loop
  1071.       if Line.Line (I) in Set_of_Lower_Case_Letters then
  1072.         Line.Line (I) := CHARACTER'VAL (CHARACTER'POS (Line.Line (I)) -
  1073.           Uc_Lc_Offset);
  1074.       end if;
  1075.     end loop;
  1076.   end Convert_to_Upper_Case;
  1077.   ----------------------------------------------------------------------
  1078.  
  1079.   -- The following procedure maintains garbage collection for the package.
  1080.  
  1081.   procedure Dispose_Lines (
  1082.     First_Old,
  1083.     Last_Old,
  1084.     First_New,
  1085.     Last_New       : in     Data_Ptr_Type;
  1086.     Dispose_Record :    out Dispose_Record_Type) is
  1087.  
  1088.     procedure Do_Dispose (
  1089.       First_Ptr,
  1090.       Last_Ptr         : in     Data_Ptr_Type;
  1091.       Lines_to_Dispose :    out NATURAL) is
  1092.  
  1093.       Curr_Ptr         : Data_Ptr_Type;
  1094.     begin
  1095.       if First_Ptr = null then
  1096.         Lines_to_Dispose := 0;
  1097.       else
  1098.         Lines_to_Dispose := (Last_Ptr.Line_Number -
  1099.                              First_Ptr.Line_Number) + 1;
  1100.  
  1101.         if Free_List_Head = null then
  1102.           Free_List_Head := First_Ptr;
  1103.           Free_List_Tail := Last_Ptr;
  1104.         else
  1105.           Free_List_Tail.Next_Line := First_Ptr;
  1106.           Free_List_Tail := Last_Ptr;
  1107.         end if;
  1108.  
  1109.         Free_List_Tail.Next_Line := null;
  1110.       end if;
  1111.     end Do_Dispose;
  1112.   begin
  1113.     Do_Dispose (First_Old, Last_Old, Dispose_Record.Old_File);
  1114.     Do_Dispose (First_New, Last_New, Dispose_Record.New_File);
  1115.   end Dispose_Lines;
  1116.   ----------------------------------------------------------------------
  1117.  
  1118.   -- The following procedure prints statistics on the list file.
  1119.  
  1120.   procedure Print_Statistics (
  1121.     Statistics : in     Statistics_Type;
  1122.     Files      : in     Files_Type;
  1123.     List_File  : in     TEXT_IO.FILE_TYPE) is
  1124.   begin
  1125.     TEXT_IO.NEW_LINE (List_File);
  1126.     TEXT_IO.PUT_LINE (List_File, "FILE COMPARE statistics:");
  1127.     TEXT_IO.NEW_LINE (List_File);
  1128.  
  1129.     if Statistics.Number_Old_Lines = 0 then
  1130.       TEXT_IO.PUT_LINE (List_File, "Old file " &
  1131.         Files.Old_File_Name (1 .. Files.Old_File_Length) &
  1132.         " has no lines.");
  1133.     elsif Statistics.Number_Old_Lines = 1 then
  1134.       TEXT_IO.PUT_LINE (List_File, "Old file " &
  1135.         Files.Old_File_Name (1 .. Files.Old_File_Length) &
  1136.         " has 1 line.");
  1137.     else
  1138.       TEXT_IO.PUT_LINE (List_File, "Old file " &
  1139.         Files.Old_File_Name (1 .. Files.Old_File_Length) &
  1140.         " has" & NATURAL'IMAGE (Statistics.Number_Old_Lines) & " lines.");
  1141.     end if;
  1142.  
  1143.     if Statistics.Number_New_Lines = 0 then
  1144.       TEXT_IO.PUT_LINE (List_File, "New file " &
  1145.         Files.New_File_Name (1 .. Files.New_File_Length) &
  1146.         " has no lines.");
  1147.     elsif Statistics.Number_New_Lines = 1 then
  1148.       TEXT_IO.PUT_LINE (List_File, "New file " &
  1149.         Files.New_File_Name (1 .. Files.New_File_Length) &
  1150.         " has 1 line.");
  1151.     else
  1152.       TEXT_IO.PUT_LINE (List_File, "New file " &
  1153.         Files.New_File_Name (1 .. Files.New_File_Length) &
  1154.         " has" & NATURAL'IMAGE (Statistics.Number_New_Lines) & " lines.");
  1155.     end if;
  1156.  
  1157.     if Statistics.Files_Equal then
  1158.       TEXT_IO.PUT_LINE (List_File, "Files are equal.");
  1159.     else
  1160.       if Statistics.Total_Equal_Lines = 0 then
  1161.         TEXT_IO.PUT_LINE (List_File, "There were no equal lines.");
  1162.       elsif Statistics.Total_Equal_Lines = 1 then
  1163.         TEXT_IO.PUT_LINE (List_File, "There was 1 equal line.");
  1164.       else
  1165.         TEXT_IO.PUT_LINE (List_File, "There were" &
  1166.           NATURAL'IMAGE (Statistics.Total_Equal_Lines) & " equal lines.");
  1167.       end if;
  1168.  
  1169.       if Statistics.Total_Minor_Changes = 0 then
  1170.         TEXT_IO.PUT_LINE (List_File,
  1171.           "There were no lines with minor changes.");
  1172.       elsif Statistics.Total_Minor_Changes = 1 then
  1173.         TEXT_IO.PUT_LINE (List_File,
  1174.           "There was 1 line with minor changes.");
  1175.       else
  1176.         TEXT_IO.PUT_LINE (List_File, "There were" &
  1177.           NATURAL'IMAGE (Statistics.Total_Minor_Changes) &
  1178.           " lines with minor changes.");
  1179.       end if;
  1180.  
  1181.       if Statistics.Total_Insertions = 0 then
  1182.         TEXT_IO.PUT_LINE (List_File, "There were no lines inserted.");
  1183.       elsif Statistics.Total_Insertions = 1 then
  1184.         TEXT_IO.PUT_LINE (List_File, "There was 1 line inserted.");
  1185.       else
  1186.         TEXT_IO.PUT_LINE (List_File, "There were" &
  1187.           NATURAL'IMAGE (Statistics.Total_Insertions) & " lines inserted.");
  1188.       end if;
  1189.  
  1190.       if Statistics.Total_Deletions = 0 then
  1191.         TEXT_IO.PUT_LINE (List_File, "There were no lines deleted.");
  1192.       elsif Statistics.Total_Deletions = 1 then
  1193.         TEXT_IO.PUT_LINE (List_File, "There was 1 line deleted.");
  1194.       else
  1195.         TEXT_IO.PUT_LINE (List_File, "There were" &
  1196.           NATURAL'IMAGE (Statistics.Total_Deletions) & " lines deleted.");
  1197.       end if;
  1198.     end if;
  1199.   end Print_Statistics;
  1200.   ----------------------------------------------------------------------
  1201.  
  1202.   -- The following function performs a generic equality comparison of
  1203.   -- data lines.  It takes into account the option of a case
  1204.   -- insensitive compare operation.
  1205.  
  1206.   function Lines_Are_Equal (
  1207.     Line1,
  1208.     Line2          : in     Data_Line_Type;
  1209.     Case_Sensitive : in     BOOLEAN) return BOOLEAN is
  1210.  
  1211.     Line1_Copy : Data_Line_Type := Line1;
  1212.     Line2_Copy : Data_Line_Type := Line2;
  1213.   begin
  1214.     if not Case_Sensitive then
  1215.       Convert_to_Upper_Case (Line1_Copy);
  1216.       Convert_to_Upper_Case (Line2_Copy);
  1217.     end if;
  1218.  
  1219.     return Line1_Copy.Line (1 .. Line1_Copy.Length) =
  1220.            Line2_Copy.Line (1 .. Line2_Copy.Length);
  1221.   end Lines_Are_Equal;
  1222.  
  1223.   -- The following procedure analyzes the files, looking for
  1224.   -- equal lines (synchronization points).  If the current lines
  1225.   -- are not equal, it simply terminates.  Otherwise it keeps
  1226.   -- looking until it finds lines that differ.
  1227.  
  1228.   procedure Analyze_Equal (
  1229.     Options            : in     Options_Type;
  1230.     List_File,
  1231.     Deck_File          : in     TEXT_IO.FILE_TYPE;
  1232.     Old_File_Head_Ptr,
  1233.     New_File_Head_Ptr  : in out Data_Ptr_Type;
  1234.     Tot_Equal_Lines    : in out NATURAL;
  1235.     Found              :    out BOOLEAN;
  1236.     Dispose_Record     :    out Dispose_Record_Type) is
  1237.  
  1238.     First_Old,
  1239.     First_New,
  1240.     Last_Old,
  1241.     Last_New             : Data_Ptr_Type;
  1242.     Local_Dispose_Record : Dispose_Record_Type := (0, 0);
  1243.  
  1244.     -- The following inner procedure outputs a group of equal
  1245.     -- lines to the list file.
  1246.  
  1247.     procedure Print_Summary_Equal (
  1248.       First_Old,
  1249.       Last_Old,
  1250.       First_New,
  1251.       Last_New   : in     Data_Ptr_Type) is
  1252.     begin
  1253.       Print_Listing (Equal_Lines_Code, First_Old, First_Old, First_New,
  1254.         First_New, List_File);
  1255.  
  1256.       if (First_Old.Line_Number + 1) < Last_Old.Line_Number then
  1257.         TEXT_IO.PUT (List_File, Equal_Lines_Code & Blank);
  1258.  
  1259.         for I in 1 .. Text_Max_Length - 2 loop
  1260.           TEXT_IO.PUT (List_File, Equal_Lines_Code);
  1261.         end loop;
  1262.  
  1263.         Int_IO.PUT (List_File,
  1264.           (Last_Old.Line_Number - First_Old.Line_Number - 1),
  1265.           List_Line_Num_Max_Length);
  1266.         TEXT_IO.PUT (List_File, " equal line");
  1267.  
  1268.         if (Last_Old.Line_Number - First_Old.Line_Number - 1) = 1 then
  1269.           TEXT_IO.PUT (List_File, Blank);
  1270.         else
  1271.           TEXT_IO.PUT (List_File, 's');
  1272.         end if;
  1273.  
  1274.         TEXT_IO.PUT (List_File, Blank);
  1275.  
  1276.         for I in 1 .. Text_Max_Length - 2 loop
  1277.           TEXT_IO.PUT (List_File, Equal_Lines_Code);
  1278.         end loop;
  1279.  
  1280.         TEXT_IO.PUT_LINE (List_File, Blank & Equal_Lines_Code);
  1281.       end if;
  1282.  
  1283.       if First_Old /= Last_Old then
  1284.         Print_Listing (Equal_Lines_Code, Last_Old, Last_Old, Last_New,
  1285.           Last_New, List_File);
  1286.       end if;
  1287.     end Print_Summary_Equal;
  1288.  
  1289.     -- The following inner procedure outputs a Copy command
  1290.     -- to the deck file.
  1291.  
  1292.     procedure Cdupdate_Equal (
  1293.       First_Old,
  1294.       Last_Old   : in     Data_Ptr_Type) is
  1295.     begin
  1296.       if Options.Verbose_Deck then
  1297.         TEXT_IO.PUT (Deck_File, Command_Code &
  1298.           Case_Conversion("Copy   ", Options.Deck_Command_Case));
  1299.       else
  1300.         TEXT_IO.PUT (Deck_File, Command_Code &
  1301.           Case_Conversion("C ", Options.Deck_Command_Case));
  1302.       end if;
  1303.  
  1304.       Int_IO.PUT (Deck_File,
  1305.         First_Old.Line_Number, List_Line_Num_Max_Length);
  1306.  
  1307.       if First_Old /= Last_Old then
  1308.         if Options.Verbose_Deck then
  1309.           TEXT_IO.PUT (Deck_File, " .. ");
  1310.         else
  1311.           TEXT_IO.PUT (Deck_File, Blank);
  1312.         end if;
  1313.  
  1314.         Int_IO.PUT (Deck_File,
  1315.           Last_Old.Line_Number, List_Line_Num_Max_Length);
  1316.       end if;
  1317.  
  1318.       TEXT_IO.NEW_LINE (Deck_File);
  1319.     end Cdupdate_Equal;
  1320.   begin  -- Analyze_Equal
  1321.     if not Lines_Are_Equal (Old_File_Head_Ptr.Data_Line,
  1322.              New_File_Head_Ptr.Data_Line, Options.Case_Sensitive) then
  1323.       Found := FALSE;  -- current lines are different
  1324.     else
  1325.       Found := TRUE;  -- current lines equal, keep looking below
  1326.  
  1327.       First_Old := Old_File_Head_Ptr;
  1328.       First_New := New_File_Head_Ptr;
  1329.  
  1330.       loop  -- iterate until lines differ
  1331.         Last_Old := Old_File_Head_Ptr;
  1332.         Last_New := New_File_Head_Ptr;
  1333.  
  1334.         Old_File_Head_Ptr := Old_File_Head_Ptr.Next_Line;
  1335.         New_File_Head_Ptr := New_File_Head_Ptr.Next_Line;
  1336.  
  1337.         exit when ((Old_File_Head_Ptr = null) or
  1338.                    (New_File_Head_Ptr = null)) or else
  1339.                   (not Lines_Are_Equal (Old_File_Head_Ptr.Data_Line,
  1340.                     New_File_Head_Ptr.Data_Line, Options.Case_Sensitive));
  1341.       end loop;
  1342.  
  1343.       if Options.Produce_Statistics then
  1344.         Tot_Equal_Lines := Tot_Equal_Lines +
  1345.           (Last_New.Line_Number - First_New.Line_Number + 1);
  1346.       end if;
  1347.  
  1348.       if Options.Produce_Listing then
  1349.         if Options.Summarize and
  1350.            (Last_New.Line_Number - First_New.Line_Number + 1 >=
  1351.             Options.Minimum_Group) then
  1352.           Print_Summary_Equal (First_Old, Last_Old, First_New,
  1353.             Last_New);
  1354.         else
  1355.           Print_Listing (Equal_Lines_Code, First_Old, Last_Old, First_New,
  1356.             Last_New, List_File);
  1357.         end if;
  1358.       end if;
  1359.  
  1360.       if Options.Produce_Deck then
  1361.         Cdupdate_Equal (First_Old, Last_Old);
  1362.       end if;
  1363.  
  1364.       Dispose_Lines (First_Old, Last_Old, First_New, Last_New,
  1365.         Local_Dispose_Record);
  1366.     end if;
  1367.  
  1368.     Dispose_Record := Local_Dispose_Record;
  1369.   end Analyze_Equal;
  1370.   ----------------------------------------------------------------------
  1371.  
  1372.   -- The following procedure finds a minor change in the current
  1373.   -- lines, and returns the position and type of minor change
  1374.   -- found.  If no minor change is found, the position 0 is returned.
  1375.  
  1376.   -- The minor change algorithms were originally written by Spencer
  1377.   -- Peterson, in Pascal pseudo-code.  The author has adopted and
  1378.   -- slightly modified these algorithms.
  1379.  
  1380.   procedure Minor_Change (
  1381.     Case_Sensitive : in     BOOLEAN;
  1382.     Str1,
  1383.     Str2           : in     Data_Line_Type;
  1384.     Pos            :    out NATURAL;
  1385.     Result         :    out Minor_Change_Found_Type) is
  1386.  
  1387.     Local_Result : Minor_Change_Found_Type;
  1388.     Str1_Copy    : Data_Line_Type := Str1;
  1389.     Str2_Copy    : Data_Line_Type := Str2;
  1390.  
  1391.     -- The following inner procedure finds a one character difference
  1392.     -- in the current lines.
  1393.  
  1394.     procedure Find_One_Char (
  1395.       Str1,
  1396.       Str2  : in     Data_Line_Type;
  1397.       Pos   :    out NATURAL;
  1398.       Found :    out BOOLEAN) is
  1399.  
  1400.       Count1,
  1401.       Count2,
  1402.       Diff_Count : NATURAL;
  1403.     begin
  1404.       Count1 := 1;
  1405.       Count2 := 1;
  1406.       Diff_Count := 0;
  1407.       Pos := 0;
  1408.  
  1409.       while (Diff_Count < 2) and (Count1 <= Str1.Length) loop
  1410.         if Str1.Line (Count1) /= Str2.Line (Count2) then
  1411.           if Diff_Count = 1 then
  1412.             Diff_Count := 2;
  1413.             Pos := 0;
  1414.           else
  1415.             Pos := Count2;
  1416.             Count2 := Count2 + 1;
  1417.             Diff_Count := 1;
  1418.           end if;
  1419.         else
  1420.           Count1 := Count1 + 1;
  1421.           Count2 := Count2 + 1;
  1422.         end if;
  1423.       end loop;
  1424.  
  1425.       if Diff_Count = 0 then
  1426.         Diff_Count := 1;
  1427.         Pos := Str2.Length;
  1428.       end if;
  1429.  
  1430.       Found := Diff_Count = 1;
  1431.     end Find_One_Char;
  1432.  
  1433.     -- The following inner procedure finds a one-character insertion
  1434.     -- in the current lines.
  1435.  
  1436.     procedure One_Char_Insert (
  1437.       Str1,
  1438.       Str2   : in     Data_Line_Type;
  1439.       Pos    :    out NATURAL;
  1440.       Result :    out Minor_Change_Found_Type) is
  1441.  
  1442.       Local_Pos : NATURAL;
  1443.       Found     : BOOLEAN;
  1444.     begin
  1445.       Find_One_Char (Str1, Str2, Local_Pos, Found);
  1446.       Pos := Local_Pos;
  1447.  
  1448.       if not Found then
  1449.         Result := No_Minor_Change_Found;
  1450.       else
  1451.         Old_Delimiter := Squote;
  1452.  
  1453.         if Str2.Line (Local_Pos) = Squote then
  1454.           New_Delimiter := Dquote;
  1455.         else
  1456.           New_Delimiter := Squote;
  1457.         end if;
  1458.  
  1459.         Result := Insertion_Found;
  1460.       end if;
  1461.     end One_Char_Insert;
  1462.  
  1463.     -- The following inner procedure finds a one-character deletion
  1464.     -- in the current lines.
  1465.  
  1466.     procedure One_Char_Delete (
  1467.       Str1,
  1468.       Str2   : in     Data_Line_Type;
  1469.       Pos    :    out NATURAL;
  1470.       Result :    out Minor_Change_Found_Type) is
  1471.  
  1472.       Local_Pos    : NATURAL;
  1473.       Found        : BOOLEAN;
  1474.     begin
  1475.       Find_One_Char (Str2, Str1, Local_Pos, Found);
  1476.       Pos := Local_Pos;
  1477.  
  1478.       if not Found then
  1479.         Result := No_Minor_Change_Found;
  1480.       else
  1481.         New_Delimiter := Squote;
  1482.  
  1483.         if Str1.Line (Local_Pos) = Squote then
  1484.           Old_Delimiter := Dquote;
  1485.         else
  1486.           Old_Delimiter := Squote;
  1487.         end if;
  1488.  
  1489.         Result := Deletion_Found;
  1490.       end if;
  1491.     end One_Char_Delete;
  1492.  
  1493.     -- The following inner procedure finds a one character replacement
  1494.     -- in the current lines.
  1495.  
  1496.     procedure One_Char_Replace (
  1497.       Str1,
  1498.       Str2   : in     Data_Line_Type;
  1499.       Pos    :    out NATURAL;
  1500.       Result :    out Minor_Change_Found_Type) is
  1501.  
  1502.       Count,
  1503.       Diff_Count,
  1504.       Local_Pos   : NATURAL;
  1505.     begin
  1506.       Count := 1;
  1507.       Diff_Count := 0;
  1508.       Local_Pos := 0;
  1509.  
  1510.       while (Diff_Count < 2) and (Count <= Str1.Length) loop
  1511.         if Str1.Line (Count) /= Str2.Line (Count) then
  1512.           Diff_Count := Diff_Count + 1;
  1513.           Local_Pos := Count;
  1514.         end if;
  1515.  
  1516.         Count := Count + 1;
  1517.       end loop;
  1518.  
  1519.       Pos := Local_Pos;
  1520.  
  1521.       if Diff_Count /= 1 then
  1522.         Result := No_Minor_Change_Found;
  1523.       else
  1524.         if Str1.Line (Local_Pos) = Squote then
  1525.           Old_Delimiter := Dquote;
  1526.         else
  1527.           Old_Delimiter := Squote;
  1528.         end if;
  1529.  
  1530.         if Str2.Line (Local_Pos) = Squote then
  1531.           New_Delimiter := Dquote;
  1532.         else
  1533.           New_Delimiter := Squote;
  1534.         end if;
  1535.  
  1536.         Result := Replacement_Found;
  1537.       end if;
  1538.     end One_Char_Replace;
  1539.  
  1540.     -- The following inner procedure finds a two-character
  1541.     -- transposition in the current lines.  Since only two
  1542.     -- delimiters for the Edit command are supported (single
  1543.     -- quote and double quote), a special case is needed to see
  1544.     -- if these two characters are being transposed.
  1545.  
  1546.     procedure Two_Char_Transpose (
  1547.       Str1,
  1548.       Str2   : in     Data_Line_Type;
  1549.       Pos    :    out NATURAL;
  1550.       Result :    out Minor_Change_Found_Type) is
  1551.  
  1552.       I,
  1553.       Diff_Count,
  1554.       Local_Pos   : NATURAL := 0;
  1555.       Found,
  1556.       Mismatched : BOOLEAN;
  1557.  
  1558.       -- The following inner function checks for the delimiter
  1559.       -- special-case transposition.
  1560.  
  1561.       function Both_Quotes_Found (
  1562.         Line : in     Data_Line_Type;
  1563.         Pos  : in     NATURAL) return BOOLEAN is
  1564.  
  1565.         Found_Squote,
  1566.         Found_Dquote  : BOOLEAN;
  1567.       begin
  1568.         Found_Squote := FALSE;
  1569.         Found_Dquote := FALSE;
  1570.  
  1571.         for I in Pos .. Pos + 1 loop
  1572.           if Line.Line (I) = Squote then
  1573.             Found_Squote := TRUE;
  1574.           elsif Line.Line (I) = Dquote then
  1575.             Found_Dquote := TRUE;
  1576.           end if;
  1577.         end loop;
  1578.  
  1579.         return (Found_Squote and Found_Dquote);
  1580.       end Both_Quotes_Found;
  1581.     begin  -- Two_Char_Transpose
  1582.       Found := FALSE;
  1583.       Mismatched := FALSE;
  1584.       I := 1;
  1585.  
  1586.       while I < Str1.Length loop
  1587.         if Str1.Line (I) /= Str2.Line (I) then
  1588.           if Found then
  1589.             Mismatched := TRUE;
  1590.             exit;
  1591.           elsif (Str1.Line (I) = Str2.Line (I+1)) and
  1592.                 (Str1.Line (I+1) = Str2.Line (I)) then
  1593.             Local_Pos := I;
  1594.             Found := TRUE;
  1595.             I := I + 1;
  1596.           else
  1597.             Mismatched := TRUE;
  1598.             exit;
  1599.           end if;
  1600.         end if;
  1601.  
  1602.         I := I + 1;
  1603.       end loop;
  1604.  
  1605.       Pos := Local_Pos;
  1606.  
  1607.       if ((not Found) or Mismatched) or else
  1608.          (Both_Quotes_Found (Str1, Local_Pos) or
  1609.           Both_Quotes_Found (Str2, Local_Pos)) then
  1610.         Result := No_Minor_Change_Found;
  1611.       else
  1612.         if (Str1.Line (Local_Pos) = Squote) or
  1613.            (Str1.Line (Local_Pos+1) = Squote) then
  1614.           Old_Delimiter := Dquote;
  1615.         else
  1616.           Old_Delimiter := Squote;
  1617.         end if;
  1618.  
  1619.         if (Str2.Line (Local_Pos) = Squote) or
  1620.            (Str2.Line (Local_Pos+1) = Squote) then
  1621.           New_Delimiter := Dquote;
  1622.         else
  1623.           New_Delimiter := Squote;
  1624.         end if;
  1625.  
  1626.         Result := Transposition_Found;
  1627.       end if;
  1628.     end Two_Char_Transpose;
  1629.   begin  -- Minor_Change
  1630.     if Case_Sensitive then
  1631.       Convert_to_Upper_Case (Str1_Copy);
  1632.       Convert_to_Upper_Case (Str2_Copy);
  1633.     end if;
  1634.  
  1635.     -- Find a minor change.  Try all appropriate possibilities.
  1636.  
  1637.     if abs (Str1_Copy.Length - Str2_Copy.Length) > 1 then
  1638.       Result := No_Minor_Change_Found;
  1639.       Pos := 0;
  1640.     elsif Str1_Copy.Length < Str2_Copy.Length then
  1641.       One_Char_Insert (Str1_Copy, Str2_Copy, Pos, Result);
  1642.     elsif Str1_Copy.Length > Str2_Copy.Length then
  1643.       One_Char_Delete (Str1_Copy, Str2_Copy, Pos, Result);
  1644.     else
  1645.       One_Char_Replace (Str1_Copy, Str2_Copy, Pos, Local_Result);
  1646.  
  1647.       if Local_Result = No_Minor_Change_Found then
  1648.         Two_Char_Transpose (Str1_Copy, Str2_Copy, Pos, Result);
  1649.       else
  1650.         Result := Local_Result;
  1651.       end if;
  1652.     end if;
  1653.   end Minor_Change;
  1654.   ----------------------------------------------------------------------
  1655.  
  1656.   -- The following function simply returns TRUE if the current lines
  1657.   -- contain a minor change, and FALSE otherwise.  It is used as an
  1658.   -- iteration terminator for other analysis routines.
  1659.  
  1660.   function Find_Minor_Change_Only (
  1661.     Old_Line,
  1662.     New_Line  : in Data_Line_Type;
  1663.     Options   : in Options_Type) return BOOLEAN is
  1664.  
  1665.     Dummy_Pos : NATURAL;
  1666.     Result    : Minor_Change_Found_Type;
  1667.   begin
  1668.     if not Options.Check_Minor_Changes then
  1669.       return FALSE;
  1670.     else
  1671.       Minor_Change (Options.Case_Sensitive, Old_line, New_Line, Dummy_Pos,
  1672.         Result);
  1673.  
  1674.       return Result /= No_Minor_Change_Found;
  1675.     end if;
  1676.   end Find_Minor_Change_Only;
  1677.   ----------------------------------------------------------------------
  1678.  
  1679.   -- The following procedure analyzes and processes all minor change
  1680.   -- requests.
  1681.  
  1682.   procedure Analyze_Minor_Change (
  1683.     Options            : in     Options_Type;
  1684.     List_File,
  1685.     Deck_File          : in     TEXT_IO.FILE_TYPE;
  1686.     Old_File_Head_Ptr,
  1687.     New_File_Head_Ptr  : in out Data_Ptr_Type;
  1688.     Tot_Minor_Changes  : in out NATURAL;
  1689.     Found              :    out BOOLEAN;
  1690.     Dispose_Record     :    out Dispose_Record_Type) is
  1691.  
  1692.     Pos                  : NATURAL;
  1693.     Minor_Change_Found   : Minor_Change_Found_Type;
  1694.     Curr_Old,
  1695.     Curr_New             : Data_Ptr_Type;
  1696.     Local_Found          : BOOLEAN;
  1697.     Local_Dispose_Record : Dispose_Record_Type := (0, 0);
  1698.  
  1699.     -- The following inner procedure emits an Edit command on the
  1700.     -- deck file.
  1701.  
  1702.     procedure Cdupdate_Minor_Change (
  1703.       Old_File_Head_Ptr,
  1704.       New_File_Head_Ptr  : in     Data_Ptr_Type;
  1705.       Minor_Change_Found : in     Minor_Change_Found_Type;
  1706.       Pos                : in     NATURAL) is
  1707.  
  1708.       procedure Print_Chars_in_Quotes (
  1709.         Str                : in     Data_Line_Type;
  1710.         Pos,
  1711.         Num_Chars_to_Print : in     NATURAL) is
  1712.       begin
  1713.         for I in 1 .. Num_Chars_to_Print loop
  1714.           TEXT_IO.PUT (Deck_File, Str.Line (I+Pos-1));
  1715.         end loop;
  1716.       end Print_Chars_in_Quotes;
  1717.     begin
  1718.       if Options.Verbose_Deck then
  1719.         TEXT_IO.PUT (Deck_File, Command_Code &
  1720.           Case_Conversion("Edit   ", Options.Deck_Command_Case));
  1721.       else
  1722.         TEXT_IO.PUT (Deck_File, Command_Code &
  1723.           Case_Conversion("Ed ", Options.Deck_Command_Case));
  1724.       end if;
  1725.  
  1726.       Int_IO.PUT (Deck_File,
  1727.         Old_File_Head_Ptr.Line_Number, List_Line_Num_Max_Length);
  1728.  
  1729.       if Options.Verbose_Deck then
  1730.         TEXT_IO.PUT (Deck_File,
  1731.           Case_Conversion(" At ", Options.Deck_Command_Case));
  1732.       else
  1733.         TEXT_IO.PUT (Deck_File, Blank);
  1734.       end if;
  1735.  
  1736.       Int_IO.PUT (Deck_File, Pos, 3);
  1737.       TEXT_IO.PUT (Deck_File, Blank & Old_Delimiter);
  1738.  
  1739.       case Minor_Change_Found is
  1740.         when Deletion_Found | Replacement_Found =>
  1741.           Print_Chars_in_Quotes (Old_File_Head_Ptr.Data_Line, Pos, 1);
  1742.         when Transposition_Found =>
  1743.           Print_Chars_in_Quotes (Old_File_Head_Ptr.Data_Line, Pos, 2);
  1744.         when others =>
  1745.           null;
  1746.       end case;
  1747.  
  1748.       if Options.Verbose_Deck then
  1749.         TEXT_IO.PUT (Deck_File, Old_Delimiter &
  1750.           Case_Conversion(" Becomes ", Options.Deck_Command_Case) & New_Delimiter);
  1751.       else
  1752.         TEXT_IO.PUT (Deck_File, Old_Delimiter & Blank & New_Delimiter);
  1753.       end if;
  1754.  
  1755.       case Minor_Change_Found is
  1756.         when Insertion_Found | Replacement_Found =>
  1757.           Print_Chars_in_Quotes (New_File_Head_Ptr.Data_Line, Pos, 1);
  1758.         when Transposition_Found =>
  1759.           Print_Chars_in_Quotes (New_File_Head_Ptr.Data_Line, Pos, 2);
  1760.         when others => null;
  1761.       end case;
  1762.  
  1763.       TEXT_IO.PUT (Deck_File, New_Delimiter);
  1764.       TEXT_IO.NEW_LINE (Deck_File);
  1765.     end Cdupdate_Minor_Change;
  1766.   begin  -- Analyze_Minor_Change
  1767.     -- Determine if a minor change exists
  1768.  
  1769.     if not Options.Check_Minor_Changes then
  1770.       Local_Found := FALSE;
  1771.     else
  1772.       Minor_Change (Options.Case_Sensitive, Old_File_Head_Ptr.Data_Line,
  1773.         New_File_Head_Ptr.Data_Line, Pos, Minor_Change_Found);
  1774.  
  1775.       Local_Found := (Minor_Change_Found /= No_Minor_Change_Found);
  1776.     end if;
  1777.  
  1778.     Found := Local_Found;
  1779.  
  1780.     -- If a minor change has been located, process it below.
  1781.  
  1782.     if Local_Found then
  1783.       if Options.Produce_Statistics then
  1784.         Tot_Minor_Changes := Tot_Minor_Changes + 1;
  1785.       end if;
  1786.  
  1787.       if Options.Produce_Listing then
  1788.         Print_Listing (Minor_Change_Code, Old_File_Head_Ptr, Old_File_Head_Ptr,
  1789.           New_File_Head_Ptr, New_File_Head_Ptr, List_File);
  1790.       end if;
  1791.  
  1792.       if Options.Produce_Deck then
  1793.         Cdupdate_Minor_Change (Old_File_Head_Ptr, New_File_Head_Ptr,
  1794.           Minor_Change_Found, Pos);
  1795.       end if;
  1796.  
  1797.       Curr_Old := Old_File_Head_Ptr;
  1798.       Curr_New := New_File_Head_Ptr;
  1799.  
  1800.       Old_File_Head_Ptr := Old_File_Head_Ptr.Next_Line;
  1801.       New_File_Head_Ptr := New_File_Head_Ptr.Next_Line;
  1802.  
  1803.       Dispose_Lines (Curr_Old, Curr_Old, Curr_New, Curr_New,
  1804.         Local_Dispose_Record);
  1805.     end if;
  1806.  
  1807.     Dispose_Record := Local_Dispose_Record;
  1808.   end Analyze_Minor_Change;
  1809.   ----------------------------------------------------------------------
  1810.  
  1811.   -- The following procedure analyzes and processes all insertion
  1812.   -- requests.
  1813.  
  1814.   procedure Analyze_Insertion (
  1815.     At_Tail_of_Old,
  1816.     At_Tail_of_New    : in     BOOLEAN;
  1817.     Options           : in     Options_Type;
  1818.     List_File,
  1819.     Deck_File         : in     TEXT_IO.FILE_TYPE;
  1820.     Old_File_Head_Ptr : in     Data_Ptr_Type;
  1821.     New_File_Head_Ptr : in out Data_Ptr_Type;
  1822.     Tot_Insertions    : in out NATURAL;
  1823.     Found             :    out BOOLEAN;
  1824.     Dispose_Record    : in out Dispose_Record_Type) is
  1825.  
  1826.     First_New,
  1827.     Last_New,
  1828.     Next_New             : Data_Ptr_Type;
  1829.     Local_Dispose_Record : Dispose_Record_Type := (0, 0);
  1830.  
  1831.     -- The following inner procedure outputs a group of insertions
  1832.     -- to the list file.
  1833.  
  1834.     procedure Print_Summary_Insertion (
  1835.       First_New,
  1836.       Last_New   : in     Data_Ptr_Type) is
  1837.     begin
  1838.       Print_Listing (Insertion_Code, null, null, First_New, First_New,
  1839.         List_File);
  1840.  
  1841.       if (First_New.Line_Number + 1) < Last_New.Line_Number then
  1842.         TEXT_IO.PUT (List_File, Insertion_Code & Blank);
  1843.  
  1844.         for I in 1 .. Text_Max_Length - 2 loop
  1845.           TEXT_IO.PUT (List_File, Insertion_Code);
  1846.         end loop;
  1847.  
  1848.         Int_IO.PUT (List_File,
  1849.           (Last_New.Line_Number - First_New.Line_Number - 1),
  1850.           List_Line_Num_Max_Length);
  1851.         TEXT_IO.PUT (List_File, " inserted line");
  1852.  
  1853.         if (Last_New.Line_Number - First_New.Line_Number - 1) = 1 then
  1854.           TEXT_IO.PUT (List_File, Blank);
  1855.         else
  1856.           TEXT_IO.PUT (List_File, 's');
  1857.         end if;
  1858.  
  1859.         TEXT_IO.PUT (List_File, Blank);
  1860.  
  1861.         for I in 1 .. Text_Max_Length - 5 loop
  1862.           TEXT_IO.PUT (List_File, Insertion_Code);
  1863.         end loop;
  1864.  
  1865.         TEXT_IO.PUT_LINE (List_File, Blank & Insertion_Code);
  1866.       end if;
  1867.  
  1868.       if First_New /= Last_New then
  1869.         Print_Listing (Insertion_Code, null, null, Last_New, Last_New,
  1870.           List_File);
  1871.       end if;
  1872.     end Print_Summary_Insertion;
  1873.  
  1874.     -- The following inner procedure emits insertion lines to the
  1875.     -- deck file.  If an insertion line begins with the same character
  1876.     -- as Command_Code, then an extra Command_Code character is
  1877.     -- emitted at the beginning (so that the CDUPDATE utility can
  1878.     -- distinguish it from a normal command).
  1879.  
  1880.     procedure Cdupdate_Insertion (
  1881.       First_New,
  1882.       Last_New   : in     Data_Ptr_Type) is
  1883.  
  1884.       Local_First_New : Data_Ptr_Type := First_New;
  1885.     begin
  1886.       loop
  1887.         if (Local_First_New.Data_Line.Length /= 0) and then
  1888.            (Local_First_New.Data_Line.Line (1) = Command_Code) then
  1889.           TEXT_IO.PUT (Deck_File, Command_Code);
  1890.         end if;
  1891.  
  1892.         TEXT_IO.PUT_LINE (Deck_File,
  1893.           Local_First_New.Data_Line.Line (1 ..
  1894.           Local_First_New.Data_Line.Length));
  1895.  
  1896.         if Local_First_New = Last_New then
  1897.           exit;
  1898.         end if;
  1899.  
  1900.         Local_First_New := Local_First_New.Next_Line;
  1901.       end loop;
  1902.     end Cdupdate_Insertion;
  1903.   begin  -- Analyze_Insertion
  1904.     -- Two cases: if the old file is exhausted, then an insertion
  1905.     -- definitely exists; else analysis must be performed.
  1906.  
  1907.     if Old_File_Head_Ptr = null then
  1908.       Found := TRUE;
  1909.  
  1910.       First_New := New_File_Head_Ptr;
  1911.       Last_New := New_File_Head_Ptr;
  1912.       New_File_Head_Ptr := null;
  1913.  
  1914.       while Last_New.Next_Line /= null loop
  1915.         Last_New := Last_New.Next_Line;
  1916.       end loop;
  1917.  
  1918.       if Options.Produce_Statistics then
  1919.         Tot_Insertions := Tot_Insertions +
  1920.           (Last_New.Line_Number - First_New.Line_Number + 1);
  1921.       end if;
  1922.  
  1923.       if Options.Produce_Listing then
  1924.         if Options.Summarize and
  1925.            (Last_New.Line_Number - First_New.Line_Number + 1 >=
  1926.             Options.Minimum_Group) then
  1927.           Print_Summary_Insertion (First_New, Last_New);
  1928.         else
  1929.           Print_Listing (Insertion_Code, null, null, First_New, Last_New,
  1930.             List_File);
  1931.         end if;
  1932.       end if;
  1933.  
  1934.       if Options.Produce_Deck then
  1935.         Cdupdate_Insertion (First_New, Last_New);
  1936.       end if;
  1937.  
  1938.       Dispose_Lines (null, null, First_New, Last_New, Local_Dispose_Record);
  1939.     else
  1940.       First_New := New_File_Head_Ptr;
  1941.       Last_New  := New_File_Head_Ptr;
  1942.       Next_New  := Last_New.Next_Line;
  1943.  
  1944.       while Next_New /= null loop
  1945.         if Lines_Are_Equal (Old_File_Head_Ptr.Data_Line,
  1946.              Next_New.Data_Line, Options.Case_Sensitive) or
  1947.            Find_Minor_Change_Only (Old_File_Head_Ptr.Data_Line,
  1948.              Next_New.Data_Line, Options) then
  1949.           exit;
  1950.         end if;
  1951.  
  1952.         Last_New := Next_New;
  1953.         Next_New := Next_New.Next_Line;
  1954.       end loop;
  1955.  
  1956.       if ((Next_New = null) and (not At_Tail_of_Old)) or
  1957.          ((Next_New = null) and (At_Tail_of_New)) then
  1958.         Found := FALSE;
  1959.       else
  1960.         Found := TRUE;
  1961.  
  1962.         New_File_Head_Ptr := Next_New;
  1963.  
  1964.         if Options.Produce_Statistics then
  1965.           Tot_Insertions := Tot_Insertions +
  1966.             (Last_New.Line_Number - First_New.Line_Number + 1);
  1967.         end if;
  1968.  
  1969.         if Options.Produce_Listing then
  1970.           if Options.Summarize and
  1971.              (Last_New.Line_Number - First_New.Line_Number + 1 >=
  1972.               Options.Minimum_Group) then
  1973.             Print_Summary_Insertion (First_New, Last_New);
  1974.           else
  1975.             Print_Listing (Insertion_Code, null, null, First_New, Last_New,
  1976.               List_File);
  1977.           end if;
  1978.         end if;
  1979.  
  1980.         if Options.Produce_Deck then
  1981.           Cdupdate_Insertion (First_New, Last_New);
  1982.         end if;
  1983.  
  1984.         Dispose_Lines (null, null, First_New, Last_New, Local_Dispose_Record);
  1985.       end if;
  1986.     end if;
  1987.  
  1988.     Dispose_Record := Local_Dispose_Record;
  1989.   end Analyze_Insertion;
  1990.   ----------------------------------------------------------------------
  1991.  
  1992.   -- The following procedure analyzes and processes all deletion
  1993.   -- requests.  This routine is only called when the current line
  1994.   -- is definitely known to be a deletion.
  1995.  
  1996.   procedure Analyze_Deletion (
  1997.     Options           : in     Options_Type;
  1998.     List_File,
  1999.     Deck_File         : in     TEXT_IO.FILE_TYPE;
  2000.     Old_File_Head_Ptr : in out Data_Ptr_Type;
  2001.     New_File_Head_Ptr : in     Data_Ptr_Type;
  2002.     Tot_Deletions     : in out NATURAL;
  2003.     Dispose_Record    :    out Dispose_Record_Type) is
  2004.  
  2005.     First_Old,
  2006.     Last_Old,
  2007.     Prev_Old,
  2008.     Curr_New             : Data_Ptr_Type;
  2009.     Local_Dispose_Record : Dispose_Record_Type := (0, 0);
  2010.  
  2011.     -- The following inner procedure outputs a group of deleted lines
  2012.     -- to the list file.
  2013.  
  2014.     procedure Print_Summary_Deletion (
  2015.       First_Old,
  2016.       Last_Old   : in     Data_Ptr_Type) is
  2017.     begin
  2018.       Print_Listing (Deletion_Code, First_Old, First_Old, null, null,
  2019.         List_File);
  2020.  
  2021.       if (First_Old.Line_Number + 1) < Last_Old.Line_Number then
  2022.         TEXT_IO.PUT (List_File, Deletion_Code & Blank);
  2023.  
  2024.         for I in 1 .. Text_Max_Length - 2 loop
  2025.           TEXT_IO.PUT (List_File, Deletion_Code);
  2026.         end loop;
  2027.  
  2028.         Int_IO.PUT (List_File,
  2029.           (Last_Old.Line_Number - First_Old.Line_Number - 1),
  2030.           List_Line_Num_Max_Length);
  2031.         TEXT_IO.PUT (List_File, " deleted line");
  2032.  
  2033.         if (Last_Old.Line_Number - First_Old.Line_Number - 1) = 1 then
  2034.           TEXT_IO.PUT (List_File, Blank);
  2035.         else
  2036.           TEXT_IO.PUT (List_File, 's');
  2037.         end if;
  2038.  
  2039.         TEXT_IO.PUT (List_File, Blank & Deletion_Code);
  2040.  
  2041.         for I in 1 .. Text_Max_Length - 5 loop
  2042.           TEXT_IO.PUT (List_File, Deletion_Code);
  2043.         end loop;
  2044.  
  2045.         TEXT_IO.PUT_LINE (List_File, Blank & Deletion_Code);
  2046.       end if;
  2047.  
  2048.       if First_Old /= Last_Old then
  2049.         Print_Listing (Deletion_Code, Last_Old, Last_Old, null, null,
  2050.           List_File);
  2051.       end if;
  2052.     end Print_Summary_Deletion;
  2053.  
  2054.     -- The following inner procedure emits a Delete command to
  2055.     -- the deck file.  This procedure is only called when the
  2056.     -- Verbose_Deck option is requested.
  2057.  
  2058.     procedure Cdupdate_Deletion (
  2059.       First_Old,
  2060.       Last_Old   : in     Data_Ptr_Type) is
  2061.     begin
  2062.       TEXT_IO.PUT (Deck_File, Command_Code &
  2063.         Case_Conversion("Delete ", Options.Deck_Command_Case));
  2064.       Int_IO.PUT (Deck_File,
  2065.         First_Old.Line_Number, List_Line_Num_Max_Length);
  2066.  
  2067.       if First_Old /= Last_Old then
  2068.         TEXT_IO.PUT (Deck_File, " .. ");
  2069.         Int_IO.PUT (Deck_File,
  2070.           Last_Old.Line_Number, List_Line_Num_Max_Length);
  2071.       end if;
  2072.  
  2073.       TEXT_IO.NEW_LINE (Deck_File);
  2074.     end Cdupdate_Deletion;
  2075.   begin  -- Analyze_Deletion
  2076.     if New_File_Head_Ptr = null then
  2077.       First_Old := Old_File_Head_Ptr;
  2078.       Last_Old := Old_File_Head_Ptr;
  2079.       Old_File_Head_Ptr := null;
  2080.  
  2081.       while Last_Old.Next_Line /= null loop
  2082.         Last_Old := Last_Old.Next_Line;
  2083.       end loop;
  2084.  
  2085.       if Options.Produce_Statistics then
  2086.         Tot_Deletions := Tot_Deletions +
  2087.           (Last_Old.Line_Number - First_Old.Line_Number + 1);
  2088.       end if;
  2089.  
  2090.       if Options.Produce_Listing then
  2091.         if Options.Summarize and
  2092.            (Last_Old.Line_Number - First_Old.Line_Number + 1 >=
  2093.             Options.Minimum_Group) then
  2094.           Print_Summary_Deletion (First_Old, Last_Old);
  2095.         else
  2096.           Print_Listing (Deletion_Code, First_Old, Last_Old, null, null,
  2097.             List_File);
  2098.         end if;
  2099.       end if;
  2100.  
  2101.       if Options.Produce_Deck and Options.Verbose_Deck then
  2102.         Cdupdate_Deletion (First_Old, Last_Old);
  2103.       end if;
  2104.  
  2105.       Dispose_Lines (First_Old, Last_Old, null, null, Local_Dispose_Record);
  2106.     else
  2107.       First_Old := Old_File_Head_Ptr;
  2108.       Last_Old  := Old_File_Head_Ptr;
  2109.       Prev_Old  := Old_File_Head_Ptr;
  2110.       Curr_New  := New_File_Head_Ptr;
  2111.  
  2112.       Outer_While_Loop :  -- used to distinguish between the inner loop below
  2113.       while (Curr_New /= null) loop
  2114.         if Lines_Are_Equal (Last_Old.Data_Line, Curr_New.Data_Line,
  2115.              Options.Case_Sensitive) or
  2116.            Find_Minor_Change_Only (Last_Old.Data_Line,
  2117.              Curr_New.Data_Line, Options) then
  2118.            exit;
  2119.         end if;
  2120.  
  2121.         while Last_Old.Next_Line /= null loop
  2122.           Prev_Old := Last_Old;
  2123.           Last_Old := Last_Old.Next_Line;
  2124.  
  2125.           if Lines_Are_Equal (Last_Old.Data_Line, Curr_New.Data_Line,
  2126.                Options.Case_Sensitive) or
  2127.              Find_Minor_Change_Only (Last_Old.Data_Line,
  2128.                Curr_New.Data_Line, Options) then
  2129.             exit Outer_While_Loop;
  2130.           end if;
  2131.         end loop;
  2132.  
  2133.         Curr_New := Curr_New.Next_Line;
  2134.         Last_Old := Old_File_Head_Ptr;
  2135.         Prev_Old := Old_File_Head_Ptr;
  2136.       end loop Outer_While_Loop;
  2137.  
  2138.       if Curr_New /= null then
  2139.         Old_File_Head_Ptr := Last_Old;
  2140.         Last_Old := Prev_Old;
  2141.       else
  2142.         Old_File_Head_Ptr := null;
  2143.  
  2144.         while Last_Old.Next_Line /= null loop
  2145.           Last_Old := Last_Old.Next_Line;
  2146.         end loop;
  2147.       end if;
  2148.  
  2149.       if Options.Produce_Statistics then
  2150.         Tot_Deletions := Tot_Deletions +
  2151.           (Last_Old.Line_Number - First_Old.Line_Number + 1);
  2152.       end if;
  2153.  
  2154.       if Options.Produce_Listing then
  2155.         if Options.Summarize and
  2156.            (Last_Old.Line_Number - First_Old.Line_Number + 1 >=
  2157.             Options.Minimum_Group) then
  2158.           Print_Summary_Deletion (First_Old, Last_Old);
  2159.         else
  2160.           Print_Listing (Deletion_Code, First_Old, Last_Old, null, null,
  2161.             List_File);
  2162.         end if;
  2163.       end if;
  2164.  
  2165.       if Options.Produce_Deck and Options.Verbose_Deck then
  2166.         Cdupdate_Deletion (First_Old, Last_Old);
  2167.       end if;
  2168.  
  2169.       Dispose_Lines (First_Old, Last_Old, null, null, Local_Dispose_Record);
  2170.     end if;
  2171.  
  2172.     Dispose_Record := Local_Dispose_Record;
  2173.   end Analyze_Deletion;
  2174.  
  2175.   -- The following procedure assigns file names to strings used
  2176.   -- internally in this package body.  This complicated assignment
  2177.   -- is required due to the nature of strings in Ada (length bounds
  2178.   -- checks).
  2179.  
  2180.   procedure Assign (
  2181.     In_File  : in     STRING;
  2182.     Out_File : in out STRING;
  2183.     Length   :    out NATURAL) is
  2184.  
  2185.     In_Last  : INTEGER := In_File'LAST;
  2186.   begin
  2187.     if In_File'LENGTH > Out_File'LENGTH then
  2188.       Out_File := In_File (In_File'FIRST .. In_File'FIRST +
  2189.         Out_File'LENGTH - 1);
  2190.       Length := Out_File'LENGTH;
  2191.     else
  2192.       Out_File (1 .. In_File'LENGTH) := In_File;
  2193.       Length := In_File'LENGTH;
  2194.     end if;
  2195.   end Assign;
  2196.   ----------------------------------------------------------------------
  2197.  
  2198.   -- The following procedure acts as a supervisor for comparing two
  2199.   -- files.  It controls the complete iteration.
  2200.  
  2201.   procedure Compare_File (
  2202.     Options    : in     Options_Type;
  2203.     Old_File,
  2204.     New_File,
  2205.     List_File,
  2206.     Deck_File  : in     TEXT_IO.FILE_TYPE;
  2207.     Files      : in out Files_Type;
  2208.     Statistics :    out Statistics_Type) is
  2209.  
  2210.     Old_File_Head_Ptr,
  2211.     New_File_Head_Ptr  : Data_Ptr_Type := null;
  2212.     Dispose_Record     : Dispose_Record_Type :=
  2213.       (Options.Lookahead, Options.Lookahead);
  2214.     Stats              : Statistics_Type := (TRUE, 1, 1, 0, 0, 0, 0);
  2215.  
  2216.     -- The following inner procedure acts as a driver for analysis
  2217.     -- of the current lines.
  2218.  
  2219.     procedure Analyze_Lines (
  2220.       Options            : in     Options_Type;
  2221.       Old_File_Head_Ptr,
  2222.       New_File_Head_Ptr  : in out Data_Ptr_Type;
  2223.       Statistics         : in out Statistics_Type;
  2224.       Dispose_Record     : in out Dispose_Record_Type) is
  2225.  
  2226.       Found : BOOLEAN;
  2227.     begin
  2228.       if Old_File_Head_Ptr = null then
  2229.         Analyze_Insertion (TEXT_IO.END_OF_FILE (Old_File),
  2230.           TEXT_IO.END_OF_FILE (New_File), Options, List_File,
  2231.           Deck_File, Old_File_Head_Ptr, New_File_Head_Ptr,
  2232.           Statistics.Total_Insertions, Found, Dispose_Record);
  2233.       elsif New_File_Head_Ptr = null then
  2234.         Analyze_Deletion (Options, List_File, Deck_File, Old_File_Head_Ptr,
  2235.           New_File_Head_Ptr, Statistics.Total_Deletions, Dispose_Record);
  2236.       else
  2237.         Analyze_Equal (Options, List_File, Deck_File, Old_File_Head_Ptr,
  2238.           New_File_Head_Ptr, Statistics.Total_Equal_Lines, Found,
  2239.           Dispose_Record);
  2240.  
  2241.         if not Found then
  2242.           Analyze_Minor_Change (Options, List_File, Deck_File,
  2243.             Old_File_Head_Ptr, New_File_Head_Ptr,
  2244.             Statistics.Total_Minor_Changes, Found, Dispose_Record);
  2245.  
  2246.           if not Found then
  2247.             Analyze_Insertion (TEXT_IO.END_OF_FILE (Old_File),
  2248.               TEXT_IO.END_OF_FILE (New_File), Options, List_File,
  2249.               Deck_File, Old_File_Head_Ptr, New_File_Head_Ptr,
  2250.               Statistics.Total_Insertions, Found, Dispose_Record);
  2251.  
  2252.             if not Found then
  2253.               Analyze_Deletion (Options, List_File, Deck_File,
  2254.                 Old_File_Head_Ptr, New_File_Head_Ptr,
  2255.                 Statistics.Total_Deletions, Dispose_Record);
  2256.             end if;
  2257.           end if;
  2258.         end if;
  2259.       end if;
  2260.     end Analyze_Lines;
  2261.   begin  -- Compare_File
  2262.     Headings (Options, List_File, Deck_File, Files);
  2263.  
  2264.     Read_File (Dispose_Record.Old_File, Old_File,
  2265.       Stats.Number_Old_Lines, Old_File_Head_Ptr);
  2266.  
  2267.     Read_File (Dispose_Record.New_File, New_File,
  2268.       Stats.Number_New_Lines, New_File_Head_Ptr);
  2269.  
  2270.     while (Old_File_Head_Ptr /= null) or
  2271.           (New_File_Head_Ptr /= null) loop
  2272.       Analyze_Lines (Options, Old_File_Head_Ptr, New_File_Head_Ptr,
  2273.         Stats, Dispose_Record);
  2274.  
  2275.       Read_File (Dispose_Record.Old_File, Old_File,
  2276.         Stats.Number_Old_Lines, Old_File_Head_Ptr);
  2277.  
  2278.       Read_File (Dispose_Record.New_File, New_File,
  2279.         Stats.Number_New_Lines, New_File_Head_Ptr);
  2280.     end loop;
  2281.  
  2282.     if Options.Produce_Deck then
  2283.       if Options.Verbose_Deck then
  2284.         TEXT_IO.PUT_LINE (Deck_File, Command_Code &
  2285.           Case_Conversion ("End", Options.Deck_Command_Case));
  2286.       else
  2287.         TEXT_IO.PUT_LINE (Deck_File, Command_Code &
  2288.           Case_Conversion ("En", Options.Deck_Command_Case));
  2289.       end if;
  2290.     end if;
  2291.  
  2292.     if not Options.Produce_Statistics then
  2293.       Stats.Number_Old_Lines := 0;
  2294.       Stats.Number_New_Lines := 0;
  2295.     else
  2296.       Stats.Files_Equal := (Stats.Total_Minor_Changes +
  2297.         Stats.Total_Insertions + Stats.Total_Deletions) = 0;
  2298.       Stats.Number_Old_Lines := Stats.Number_Old_Lines - 1;
  2299.       Stats.Number_New_Lines := Stats.Number_New_Lines - 1;
  2300.  
  2301.       if Options.Produce_Listing then
  2302.         Print_Statistics (Stats, Files, List_File);
  2303.       end if;
  2304.     end if;
  2305.  
  2306.     Statistics := Stats;
  2307.   end Compare_File;
  2308.   ----------------------------------------------------------------------
  2309.  
  2310.   -- The following procedure acts as a supervisor for the quick
  2311.   -- compare operation.
  2312.  
  2313.   procedure Quick_Compare_File (
  2314.     Case_Sensitive : in     BOOLEAN;
  2315.     Old_File,
  2316.     New_File       : in     TEXT_IO.FILE_TYPE;
  2317.     Files_Equal    :    out BOOLEAN) is
  2318.  
  2319.     Old_Line,
  2320.     New_Line    : Line_Type;
  2321.     Old_Length,
  2322.     New_Length  : NATURAL;
  2323.   begin
  2324.     while (not TEXT_IO.END_OF_FILE (Old_File)) and
  2325.           (not TEXT_IO.END_OF_FILE (New_File)) loop
  2326.       begin
  2327.         Get_A_Line (Old_File, Old_Line, Old_Length);
  2328.         Get_A_Line (New_File, New_Line, New_Length);
  2329.       exception
  2330.         when CONSTRAINT_ERROR =>
  2331.           raise Line_Length_Error;
  2332.       end;
  2333.  
  2334.       if (TEXT_IO.END_OF_FILE (Old_File) or
  2335.           TEXT_IO.END_OF_FILE (New_File)) or else
  2336.          (not Lines_Are_Equal ((Old_Line, Old_Length),
  2337.                 (New_Line, New_Length), Case_Sensitive)) then
  2338.         exit;
  2339.       end if;
  2340.     end loop;
  2341.  
  2342.     Files_Equal := (TEXT_IO.END_OF_FILE (Old_File) and
  2343.                     TEXT_IO.END_OF_FILE (New_File)) and then
  2344.                    Lines_Are_Equal ((Old_Line, Old_Length),
  2345.                      (New_Line, New_Length), Case_Sensitive);
  2346.   end Quick_Compare_File;
  2347.   ----------------------------------------------------------------------
  2348.  
  2349.   -- The following procedure is visible to all users, and is used
  2350.   -- for normal compare operations, where a statistics record is
  2351.   -- requested.
  2352.  
  2353.   procedure Compare (
  2354.     Old_File,
  2355.     New_File,
  2356.     List_File,
  2357.     Deck_File  : in     TEXT_IO.FILE_TYPE;
  2358.     Statistics :    out Statistics_Type;
  2359.     Options    : in     Options_Type := Default_Options) is
  2360.  
  2361.     Files : Files_Type;
  2362.   begin
  2363.     if (not TEXT_IO.IS_OPEN (Old_File)) or else
  2364.        (TEXT_IO.MODE (Old_File) /= TEXT_IO.IN_FILE) then
  2365.       raise Old_File_Open_Error;
  2366.     end if;
  2367.  
  2368.     if (not TEXT_IO.IS_OPEN (New_File)) or else
  2369.        (TEXT_IO.MODE (New_File) /= TEXT_IO.IN_FILE) then
  2370.       raise New_File_Open_Error;
  2371.     end if;
  2372.  
  2373.     if Options.Produce_Listing and then
  2374.        ((not TEXT_IO.IS_OPEN (List_File)) or else
  2375.         (TEXT_IO.MODE (List_File) /= TEXT_IO.OUT_FILE)) then
  2376.       raise List_File_Create_Error;
  2377.     end if;
  2378.  
  2379.     if Options.Produce_Deck and then
  2380.        ((not TEXT_IO.IS_OPEN (Deck_File)) or else
  2381.         (TEXT_IO.MODE (Deck_File) /= TEXT_IO.OUT_FILE)) then
  2382.       raise Deck_File_Create_Error;
  2383.     end if;
  2384.  
  2385.     Assign (TEXT_IO.NAME (Old_File), Files.Old_File_Name,
  2386.       Files.Old_File_Length);
  2387.     Assign (TEXT_IO.NAME (New_File), Files.New_File_Name,
  2388.       Files.New_File_Length);
  2389.  
  2390.     if not Options.Produce_Listing then
  2391.       Files.List_File_Name := (others => Blank);
  2392.       Files.List_File_Length := 0;
  2393.     else
  2394.       Assign (TEXT_IO.NAME (List_File), Files.List_File_Name,
  2395.         Files.List_File_Length);
  2396.     end if;
  2397.  
  2398.     if not Options.Produce_Deck then
  2399.       Files.Deck_File_Name := (others => Blank);
  2400.       Files.Deck_File_Length := 0;
  2401.     else
  2402.       Assign (TEXT_IO.NAME (Deck_File), Files.Deck_File_Name,
  2403.         Files.Deck_File_Length);
  2404.     end if;
  2405.  
  2406.     if Options.Wide_Listing then
  2407.       Text_Max_Length := Large_Margin;
  2408.     else
  2409.       Text_Max_Length := Small_Margin;
  2410.     end if;
  2411.  
  2412.     Compare_File (Options, Old_File, New_File, List_File,
  2413.       Deck_File, Files, Statistics);
  2414.   end Compare;
  2415.   ----------------------------------------------------------------------
  2416.  
  2417.   -- The following procedure is visible to all users, and is used for
  2418.   -- normal compare operations where a statistics record is not
  2419.   -- required.  Statistics may still be printed on the list file,
  2420.   -- if the user selects that option.
  2421.  
  2422.   procedure Compare (
  2423.     Old_File,
  2424.     New_File,
  2425.     List_File,
  2426.     Deck_File  : in     TEXT_IO.FILE_TYPE;
  2427.     Options    : in     Options_Type := Default_Options) is
  2428.  
  2429.     Dummy_Statistics : Statistics_Type;
  2430.   begin
  2431.     Compare (Old_File, New_File, List_File, Deck_File, Dummy_Statistics,
  2432.       Options);
  2433.   end Compare;
  2434.   ----------------------------------------------------------------------
  2435.  
  2436.   -- The following function is visible to all users, and is used for
  2437.   -- quick compare operations.
  2438.  
  2439.   function Quick_Compare (
  2440.     Old_File,
  2441.     New_File        : in     TEXT_IO.FILE_TYPE;
  2442.     Case_Sensitive  : in     BOOLEAN := TRUE) return BOOLEAN is
  2443.  
  2444.     Files   : Files_Type;
  2445.     Result  : BOOLEAN;
  2446.   begin
  2447.     if (not TEXT_IO.IS_OPEN (Old_File)) or else
  2448.        (TEXT_IO.MODE (Old_File) /= TEXT_IO.IN_FILE) then
  2449.       raise Old_File_Open_Error;
  2450.     end if;
  2451.  
  2452.     if (not TEXT_IO.IS_OPEN (New_File)) or else
  2453.        (TEXT_IO.MODE (New_File) /= TEXT_IO.IN_FILE) then
  2454.       raise New_File_Open_Error;
  2455.     end if;
  2456.  
  2457.     Quick_Compare_File (Case_Sensitive, Old_File, New_File, Result);
  2458.  
  2459.     return Result;
  2460.   end Quick_Compare;
  2461.   ----------------------------------------------------------------------
  2462.  
  2463.   -- The following procedure is visible to all users, and is used
  2464.   -- for normal compare operations, where a statistics record is
  2465.   -- requested.
  2466.  
  2467.   procedure Compare (
  2468.     Old_File_Name,
  2469.     New_File_Name,
  2470.     List_File_Name,
  2471.     Deck_File_Name  : in     STRING;
  2472.     Statistics      :    out Statistics_Type;
  2473.     Options         : in     Options_Type := Default_Options) is
  2474.  
  2475.     Old_File,
  2476.     New_File,
  2477.     List_File,
  2478.     Deck_File  : TEXT_IO.FILE_TYPE;
  2479.   begin
  2480.     if (Old_File_Name'LENGTH  > Maximum_File_Name_Length) or
  2481.        (New_File_Name'LENGTH  > Maximum_File_Name_Length) or
  2482.        (List_File_Name'LENGTH > Maximum_File_Name_Length) or
  2483.        (Deck_File_Name'LENGTH > Maximum_File_Name_Length) then
  2484.       raise File_Name_Length_Error;
  2485.     end if;
  2486.  
  2487.     begin
  2488.       TEXT_IO.OPEN (Old_File, TEXT_IO.IN_FILE, Old_File_Name);
  2489.     exception
  2490.       when TEXT_IO.STATUS_ERROR | TEXT_IO.NAME_ERROR | TEXT_IO.USE_ERROR =>
  2491.         raise Old_File_Open_Error;
  2492.     end;
  2493.  
  2494.     begin
  2495.       TEXT_IO.OPEN (New_File, TEXT_IO.IN_FILE, New_File_Name);
  2496.     exception
  2497.       when TEXT_IO.STATUS_ERROR | TEXT_IO.NAME_ERROR | TEXT_IO.USE_ERROR =>
  2498.         raise New_File_Open_Error;
  2499.     end;
  2500.  
  2501.     if Options.Produce_Listing then
  2502.       begin
  2503.         TEXT_IO.CREATE (List_File, TEXT_IO.OUT_FILE, List_File_Name);
  2504.       exception
  2505.         when TEXT_IO.STATUS_ERROR | TEXT_IO.NAME_ERROR | TEXT_IO.USE_ERROR =>
  2506.           raise List_File_Create_Error;
  2507.       end;
  2508.     end if;
  2509.  
  2510.     if Options.Produce_Deck then
  2511.       begin
  2512.         TEXT_IO.CREATE (Deck_File, TEXT_IO.OUT_FILE, Deck_File_Name);
  2513.       exception
  2514.         when TEXT_IO.STATUS_ERROR | TEXT_IO.NAME_ERROR | TEXT_IO.USE_ERROR =>
  2515.           raise Deck_File_Create_Error;
  2516.       end;
  2517.     end if;
  2518.  
  2519.     Compare (Old_File, New_File, List_File, Deck_File, Statistics,
  2520.       Options);
  2521.  
  2522.     TEXT_IO.CLOSE (Old_File);
  2523.     TEXT_IO.CLOSE (New_File);
  2524.  
  2525.     if Options.Produce_Listing then
  2526.       TEXT_IO.CLOSE (List_File);
  2527.     end if;
  2528.  
  2529.     if Options.Produce_Deck then
  2530.       TEXT_IO.CLOSE (Deck_File);
  2531.     end if;
  2532.   end Compare;
  2533.   ----------------------------------------------------------------------
  2534.  
  2535.   -- The following procedure is visible to all users, and is used for
  2536.   -- normal compare operations where a statistics record is not
  2537.   -- required.  Statistics may still be printed on the list file,
  2538.   -- if the user selects that option.
  2539.  
  2540.   procedure Compare (
  2541.     Old_File_Name,
  2542.     New_File_Name,
  2543.     List_File_Name,
  2544.     Deck_File_Name  : in     STRING;
  2545.     Options         : in     Options_Type := Default_Options) is
  2546.  
  2547.     Dummy_Statistics : Statistics_Type;
  2548.   begin
  2549.     Compare (Old_File_Name, New_File_Name, List_File_Name, Deck_File_Name,
  2550.       Dummy_Statistics, Options);
  2551.   end Compare;
  2552.   ----------------------------------------------------------------------
  2553.  
  2554.   -- The following function is visible to all users, and is used for
  2555.   -- quick compare operations.
  2556.  
  2557.   function Quick_Compare (
  2558.     Old_File_Name,
  2559.     New_File_Name   : in     STRING;
  2560.     Case_Sensitive  : in     BOOLEAN := TRUE) return BOOLEAN is
  2561.  
  2562.     Old_File,
  2563.     New_File  : TEXT_IO.FILE_TYPE;
  2564.     Result    : BOOLEAN;
  2565.   begin
  2566.     begin
  2567.       TEXT_IO.OPEN (Old_File, TEXT_IO.IN_FILE, Old_File_Name);
  2568.     exception
  2569.       when TEXT_IO.STATUS_ERROR | TEXT_IO.NAME_ERROR | TEXT_IO.USE_ERROR =>
  2570.         raise Old_File_Open_Error;
  2571.     end;
  2572.  
  2573.     begin
  2574.       TEXT_IO.OPEN (New_File, TEXT_IO.IN_FILE, New_File_Name);
  2575.     exception
  2576.       when TEXT_IO.STATUS_ERROR | TEXT_IO.NAME_ERROR | TEXT_IO.USE_ERROR =>
  2577.         raise New_File_Open_Error;
  2578.     end;
  2579.  
  2580.     Result := Quick_Compare (Old_File, New_File, Case_Sensitive);
  2581.  
  2582.     TEXT_IO.CLOSE (Old_File);
  2583.     TEXT_IO.CLOSE (New_File);
  2584.  
  2585.     return Result;
  2586.   end Quick_Compare;
  2587. end File_Compare_Utilities;
  2588.