OSDN Git Service

8a72c4e3bb67ab30155dd9df92e7f521ca5b2126
[pf3gnuchains/gcc-fork.git] / gcc / ada / gnatchop.adb
1 ------------------------------------------------------------------------------
2 --                                                                          --
3 --                         GNAT COMPILER COMPONENTS                         --
4 --                                                                          --
5 --                             G N A T C H O P                              --
6 --                                                                          --
7 --                                 B o d y                                  --
8 --                                                                          --
9 --          Copyright (C) 1998-2007, Free Software Foundation, Inc.         --
10 --                                                                          --
11 -- GNAT is free software;  you can  redistribute it  and/or modify it under --
12 -- terms of the  GNU General Public License as published  by the Free Soft- --
13 -- ware  Foundation;  either version 3,  or (at your option) any later ver- --
14 -- sion.  GNAT is distributed in the hope that it will be useful, but WITH- --
15 -- OUT ANY WARRANTY;  without even the  implied warranty of MERCHANTABILITY --
16 -- or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License --
17 -- for  more details.  You should have  received  a copy of the GNU General --
18 -- Public License  distributed with GNAT; see file COPYING3.  If not, go to --
19 -- http://www.gnu.org/licenses for a complete copy of the license.          --
20 --                                                                          --
21 -- GNAT was originally developed  by the GNAT team at  New York University. --
22 -- Extensive contributions were provided by Ada Core Technologies Inc.      --
23 --                                                                          --
24 ------------------------------------------------------------------------------
25
26 with Ada.Characters.Conversions; use Ada.Characters.Conversions;
27 with Ada.Command_Line;           use Ada.Command_Line;
28 with Ada.Directories;            use Ada.Directories;
29 with Ada.Streams.Stream_IO;      use Ada.Streams;
30 with Ada.Text_IO;                use Ada.Text_IO;
31 with System.CRTL;                use System; use System.CRTL;
32
33 with GNAT.Command_Line;          use GNAT.Command_Line;
34 with GNAT.OS_Lib;                use GNAT.OS_Lib;
35 with GNAT.Heap_Sort_G;
36 with GNAT.Table;
37
38 with Hostparm;
39 with Switch;   use Switch;
40 with Types;
41
42 procedure Gnatchop is
43
44    Config_File_Name : constant String_Access := new String'("gnat.adc");
45    --  The name of the file holding the GNAT configuration pragmas
46
47    Gcc : String_Access := new String'("gcc");
48    --  May be modified by switch --GCC=
49
50    Gcc_Set : Boolean := False;
51    --  True if a switch --GCC= is used
52
53    Gnat_Cmd : String_Access;
54    --  Command to execute the GNAT compiler
55
56    Gnat_Args : Argument_List_Access :=
57                  new Argument_List'
58                    (new String'("-c"),
59                     new String'("-x"),
60                     new String'("ada"),
61                     new String'("-gnats"),
62                     new String'("-gnatu"));
63    --  Arguments used in Gnat_Cmd call
64
65    EOF : constant Character := Character'Val (26);
66    --  Special character to signal end of file. Not required in input
67    --  files, but properly treated if present. Not generated in output
68    --  files except as a result of copying input file.
69
70    --------------------
71    -- File arguments --
72    --------------------
73
74    subtype File_Num is Natural;
75    subtype File_Offset is Natural;
76
77    type File_Entry is record
78       Name : String_Access;
79       --  Name of chop file or directory
80
81       SR_Name : String_Access;
82       --  Null unless the chop file starts with a source reference pragma
83       --  in which case this field points to the file name from this pragma.
84    end record;
85
86    package File is new GNAT.Table
87      (Table_Component_Type => File_Entry,
88       Table_Index_Type     => File_Num,
89       Table_Low_Bound      => 1,
90       Table_Initial        => 100,
91       Table_Increment      => 100);
92
93    Directory : String_Access;
94    --  Record name of directory, or a null string if no directory given
95
96    Compilation_Mode  : Boolean := False;
97    Overwrite_Files   : Boolean := False;
98    Preserve_Mode     : Boolean := False;
99    Quiet_Mode        : Boolean := False;
100    Source_References : Boolean := False;
101    Verbose_Mode      : Boolean := False;
102    Exit_On_Error     : Boolean := False;
103    --  Global options
104
105    Write_gnat_adc : Boolean := False;
106    --  Gets set true if we append to gnat.adc or create a new gnat.adc.
107    --  Used to inhibit complaint about no units generated.
108
109    ---------------
110    -- Unit list --
111    ---------------
112
113    type Line_Num is new Natural;
114    --  Line number (for source reference pragmas)
115
116    type Unit_Count_Type  is new Integer;
117    subtype Unit_Num      is Unit_Count_Type range 1 .. Unit_Count_Type'Last;
118    --  Used to refer to unit number in unit table
119
120    type SUnit_Num is new Integer;
121    --  Used to refer to entry in sorted units table. Note that entry
122    --  zero is only for use by Heapsort, and is not otherwise referenced.
123
124    type Unit_Kind is (Unit_Spec, Unit_Body, Config_Pragmas);
125
126    --  Structure to contain all necessary information for one unit.
127    --  Entries are also temporarily used to record config pragma sequences.
128
129    type Unit_Info is record
130       File_Name : String_Access;
131       --  File name from GNAT output line
132
133       Chop_File : File_Num;
134       --  File number in chop file sequence
135
136       Start_Line : Line_Num;
137       --  Line number from GNAT output line
138
139       Offset : File_Offset;
140       --  Offset name from GNAT output line
141
142       SR_Present : Boolean;
143       --  Set True if SR parameter present
144
145       Length : File_Offset;
146       --  A length of 0 means that the Unit is the last one in the file
147
148       Kind : Unit_Kind;
149       --  Indicates kind of unit
150
151       Sorted_Index : SUnit_Num;
152       --  Index of unit in sorted unit list
153
154       Bufferg : String_Access;
155       --  Pointer to buffer containing configuration pragmas to be
156       --  prepended. Null if no pragmas to be prepended.
157    end record;
158
159    --  The following table stores the unit offset information
160
161    package Unit is new GNAT.Table
162      (Table_Component_Type => Unit_Info,
163       Table_Index_Type     => Unit_Count_Type,
164       Table_Low_Bound      => 1,
165       Table_Initial        => 500,
166       Table_Increment      => 100);
167
168    --  The following table is used as a sorted index to the Unit.Table.
169    --  The entries in Unit.Table are not moved, instead we just shuffle
170    --  the entries in Sorted_Units. Note that the zeroeth entry in this
171    --  table is used by GNAT.Heap_Sort_G.
172
173    package Sorted_Units is new GNAT.Table
174      (Table_Component_Type => Unit_Num,
175       Table_Index_Type     => SUnit_Num,
176       Table_Low_Bound      => 0,
177       Table_Initial        => 500,
178       Table_Increment      => 100);
179
180    function Is_Duplicated (U : SUnit_Num) return Boolean;
181    --  Returns true if U is duplicated by a later unit.
182    --  Note that this function returns false for the last entry.
183
184    procedure Sort_Units;
185    --  Sort units and set up sorted unit table
186
187    ----------------------
188    -- File_Descriptors --
189    ----------------------
190
191    function dup  (handle   : File_Descriptor) return File_Descriptor;
192    function dup2 (from, to : File_Descriptor) return File_Descriptor;
193
194    ---------------------
195    -- Local variables --
196    ---------------------
197
198    Warning_Count : Natural := 0;
199    --  Count of warnings issued so far
200
201    -----------------------
202    -- Local subprograms --
203    -----------------------
204
205    procedure Error_Msg (Message : String; Warning : Boolean := False);
206    --  Produce an error message on standard error output
207
208    procedure File_Time_Stamp (Name : C_File_Name; Time : OS_Time);
209    --  Given the name of a file or directory, Name, set the
210    --  time stamp. This function must be used for an unopened file.
211
212    function Files_Exist return Boolean;
213    --  Check Unit.Table for possible file names that already exist
214    --  in the file system. Returns true if files exist, False otherwise
215
216    function Get_Maximum_File_Name_Length return Integer;
217    pragma Import (C, Get_Maximum_File_Name_Length,
218                  "__gnat_get_maximum_file_name_length");
219    --  Function to get maximum file name length for system
220
221    Maximum_File_Name_Length : constant Integer := Get_Maximum_File_Name_Length;
222    Maximum_File_Name_Length_String : constant String :=
223                                        Integer'Image
224                                          (Maximum_File_Name_Length);
225
226    function Locate_Executable
227      (Program_Name    : String;
228       Look_For_Prefix : Boolean := True) return String_Access;
229    --  Locate executable for given program name. This takes into account
230    --  the target-prefix of the current command, if Look_For_Prefix is True.
231
232    subtype EOL_Length is Natural range 0 .. 2;
233    --  Possible lengths of end of line sequence
234
235    type EOL_String (Len : EOL_Length := 0) is record
236       Str : String (1 .. Len);
237    end record;
238
239    function Get_EOL
240      (Source : not null access String;
241       Start  : Positive) return EOL_String;
242    --  Return the line terminator used in the passed string
243
244    procedure Parse_EOL
245      (Source : not null access String;
246       Ptr    : in out Positive);
247    --  On return Source (Ptr) is the first character of the next line
248    --  or EOF. Source.all must be terminated by EOF.
249
250    function Parse_File (Num : File_Num) return Boolean;
251    --  Calls the GNAT compiler to parse the given source file and parses the
252    --  output using Parse_Offset_Info. Returns True if parse operation
253    --  completes, False if some system error (e.g. failure to read the
254    --  offset information) occurs.
255
256    procedure Parse_Offset_Info
257      (Chop_File : File_Num;
258       Source    : not null access String);
259    --  Parses the output of the compiler indicating the offsets
260    --  and names of the compilation units in Chop_File.
261
262    procedure Parse_Token
263      (Source    : not null access String;
264       Ptr       : in out Positive;
265       Token_Ptr : out Positive);
266    --  Skips any separators and stores the start of the token in Token_Ptr.
267    --  Then stores the position of the next separator in Ptr.
268    --  On return Source (Token_Ptr .. Ptr - 1) is the token.
269
270    procedure Read_File
271      (FD       : File_Descriptor;
272       Contents : out String_Access;
273       Success  : out Boolean);
274    --  Reads file associated with FS into the newly allocated
275    --  string Contents.
276    --  [VMS] Success is true iff the number of bytes read is less than or
277    --   equal to the file size.
278    --  [Other] Success is true iff the number of bytes read is equal to
279    --   the file size.
280
281    function Report_Duplicate_Units return Boolean;
282    --  Output messages about duplicate units in the input files in Unit.Table
283    --  Returns True if any duplicates found, Fals if no duplicates found.
284
285    function Scan_Arguments return Boolean;
286    --  Scan command line options and set global variables accordingly.
287    --  Also scan out file and directory arguments. Returns True if scan
288    --  was successful, and False if the scan fails for any reason.
289
290    procedure Usage;
291    --  Output message on standard output describing syntax of gnatchop command
292
293    procedure Warning_Msg (Message : String);
294    --  Output a warning message on standard error and update warning count
295
296    function Write_Chopped_Files (Input : File_Num) return Boolean;
297    --  Write all units that result from chopping the Input file
298
299    procedure Write_Config_File (Input : File_Num; U : Unit_Num);
300    --  Call to write configuration pragmas (append them to gnat.adc)
301    --  Input is the file number for the chop file and U identifies the
302    --  unit entry for the configuration pragmas.
303
304    function Get_Config_Pragmas
305      (Input : File_Num;
306       U     : Unit_Num) return  String_Access;
307    --  Call to read configuration pragmas from given unit entry, and
308    --  return a buffer containing the pragmas to be appended to
309    --  following units. Input is the file number for the chop file and
310    --  U identifies the unit entry for the configuration pragmas.
311
312    procedure Write_Source_Reference_Pragma
313      (Info    : Unit_Info;
314       Line    : Line_Num;
315       File    : Stream_IO.File_Type;
316       EOL     : EOL_String;
317       Success : in out Boolean);
318    --  If Success is True on entry, writes a source reference pragma using
319    --  the chop file from Info, and the given line number. On return Success
320    --  indicates whether the write succeeded. If Success is False on entry,
321    --  or if the global flag Source_References is False, then the call to
322    --  Write_Source_Reference_Pragma has no effect. EOL indicates the end
323    --  of line sequence to be written at the end of the pragma.
324
325    procedure Write_Unit
326      (Source  : not null access String;
327       Num     : Unit_Num;
328       TS_Time : OS_Time;
329       Success : out Boolean);
330    --  Write one compilation unit of the source to file
331
332    ---------
333    -- dup --
334    ---------
335
336    function dup (handle : File_Descriptor) return File_Descriptor is
337    begin
338       return File_Descriptor (System.CRTL.dup (int (handle)));
339    end dup;
340
341    ----------
342    -- dup2 --
343    ----------
344
345    function dup2 (from, to : File_Descriptor) return File_Descriptor is
346    begin
347       return File_Descriptor (System.CRTL.dup2 (int (from), int (to)));
348    end dup2;
349
350    ---------------
351    -- Error_Msg --
352    ---------------
353
354    procedure Error_Msg (Message : String; Warning : Boolean := False) is
355    begin
356       Put_Line (Standard_Error, Message);
357
358       if not Warning then
359          Set_Exit_Status (Failure);
360
361          if Exit_On_Error then
362             raise Types.Terminate_Program;
363          end if;
364       end if;
365    end Error_Msg;
366
367    ---------------------
368    -- File_Time_Stamp --
369    ---------------------
370
371    procedure File_Time_Stamp (Name : C_File_Name; Time : OS_Time) is
372       procedure Set_File_Time (Name : C_File_Name; Time : OS_Time);
373       pragma Import (C, Set_File_Time, "__gnat_set_file_time_name");
374
375    begin
376       Set_File_Time (Name, Time);
377    end File_Time_Stamp;
378
379    -----------------
380    -- Files_Exist --
381    -----------------
382
383    function Files_Exist return Boolean is
384       Exists : Boolean := False;
385
386    begin
387       for SNum in 1 .. SUnit_Num (Unit.Last) loop
388
389          --  Only check and report for the last instance of duplicated files
390
391          if not Is_Duplicated (SNum) then
392             declare
393                Info : constant Unit_Info :=
394                         Unit.Table (Sorted_Units.Table (SNum));
395
396             begin
397                if Is_Writable_File (Info.File_Name.all) then
398                   if Hostparm.OpenVMS then
399                      Error_Msg
400                        (Info.File_Name.all
401                         & " already exists, use /OVERWRITE to overwrite");
402                   else
403                      Error_Msg (Info.File_Name.all
404                                  & " already exists, use -w to overwrite");
405                   end if;
406
407                   Exists := True;
408                end if;
409             end;
410          end if;
411       end loop;
412
413       return Exists;
414    end Files_Exist;
415
416    ------------------------
417    -- Get_Config_Pragmas --
418    ------------------------
419
420    function Get_Config_Pragmas
421      (Input : File_Num;
422       U     : Unit_Num)
423       return  String_Access
424    is
425       Info    : Unit_Info renames Unit.Table (U);
426       FD      : File_Descriptor;
427       Name    : aliased constant String :=
428                   File.Table (Input).Name.all & ASCII.Nul;
429       Length  : File_Offset;
430       Buffer  : String_Access;
431       Result  : String_Access;
432
433       Success : Boolean;
434       pragma Warnings (Off, Success);
435
436    begin
437       FD := Open_Read (Name'Address, Binary);
438
439       if FD = Invalid_FD then
440          Error_Msg ("cannot open " & File.Table (Input).Name.all);
441          return null;
442       end if;
443
444       Read_File (FD, Buffer, Success);
445
446       --  A length of 0 indicates that the rest of the file belongs to
447       --  this unit. The actual length must be calculated now. Take into
448       --  account that the last character (EOF) must not be written.
449
450       if Info.Length = 0 then
451          Length := Buffer'Last - (Buffer'First + Info.Offset);
452       else
453          Length := Info.Length;
454       end if;
455
456       Result := new String'(Buffer (1 .. Length));
457       Close (FD);
458       return Result;
459    end Get_Config_Pragmas;
460
461    -------------
462    -- Get_EOL --
463    -------------
464
465    function Get_EOL
466      (Source : not null access String;
467       Start  : Positive)
468       return   EOL_String
469    is
470       Ptr   : Positive := Start;
471       First : Positive;
472       Last  : Natural;
473
474    begin
475       --  Skip to end of line
476
477       while Source (Ptr) /= ASCII.CR and then
478             Source (Ptr) /= ASCII.LF and then
479             Source (Ptr) /= EOF
480       loop
481          Ptr := Ptr + 1;
482       end loop;
483
484       Last  := Ptr;
485
486       if Source (Ptr) /= EOF then
487
488          --  Found CR or LF
489
490          First := Ptr;
491
492       else
493          First := Ptr + 1;
494       end if;
495
496       --  Recognize CR/LF or LF/CR combination
497
498       if (Source (Ptr + 1) = ASCII.CR or Source (Ptr + 1) = ASCII.LF)
499          and then Source (Ptr) /= Source (Ptr + 1)
500       then
501          Last := First + 1;
502       end if;
503
504       return (Len => Last + 1 - First, Str => Source (First .. Last));
505    end Get_EOL;
506
507    -------------------
508    -- Is_Duplicated --
509    -------------------
510
511    function Is_Duplicated (U : SUnit_Num) return Boolean is
512    begin
513       return U < SUnit_Num (Unit.Last)
514         and then
515           Unit.Table (Sorted_Units.Table (U)).File_Name.all =
516           Unit.Table (Sorted_Units.Table (U + 1)).File_Name.all;
517    end Is_Duplicated;
518
519    -----------------------
520    -- Locate_Executable --
521    -----------------------
522
523    function Locate_Executable
524      (Program_Name    : String;
525       Look_For_Prefix : Boolean := True) return String_Access
526    is
527       Current_Command : constant String := Normalize_Pathname (Command_Name);
528       End_Of_Prefix   : Natural;
529       Start_Of_Prefix : Positive;
530       Result          : String_Access;
531
532    begin
533       Start_Of_Prefix := Current_Command'First;
534       End_Of_Prefix   := Start_Of_Prefix - 1;
535
536       if Look_For_Prefix then
537
538          --  Find Start_Of_Prefix
539
540          for J in reverse Current_Command'Range loop
541             if Current_Command (J) = '/' or
542               Current_Command (J) = Directory_Separator or
543               Current_Command (J) = ':'
544             then
545                Start_Of_Prefix := J + 1;
546                exit;
547             end if;
548          end loop;
549
550          --  Find End_Of_Prefix
551
552          for J in reverse Start_Of_Prefix .. Current_Command'Last loop
553             if Current_Command (J) = '-' then
554                End_Of_Prefix := J;
555                exit;
556             end if;
557          end loop;
558       end if;
559
560       declare
561          Command : constant String :=
562                      Current_Command (Start_Of_Prefix .. End_Of_Prefix) &
563                                                                 Program_Name;
564       begin
565          Result := Locate_Exec_On_Path (Command);
566
567          if Result = null then
568             Error_Msg
569               (Command & ": installation problem, executable not found");
570          end if;
571       end;
572
573       return Result;
574    end Locate_Executable;
575
576    ---------------
577    -- Parse_EOL --
578    ---------------
579
580    procedure Parse_EOL
581      (Source : not null access String;
582       Ptr    : in out Positive) is
583    begin
584       --  Skip to end of line
585
586       while Source (Ptr) /= ASCII.CR and then Source (Ptr) /= ASCII.LF
587         and then Source (Ptr) /= EOF
588       loop
589          Ptr := Ptr + 1;
590       end loop;
591
592       if Source (Ptr) /= EOF then
593          Ptr := Ptr + 1;      -- skip CR or LF
594       end if;
595
596       --  Skip past CR/LF or LF/CR combination
597
598       if (Source (Ptr) = ASCII.CR or Source (Ptr) = ASCII.LF)
599          and then Source (Ptr) /= Source (Ptr - 1)
600       then
601          Ptr := Ptr + 1;
602       end if;
603    end Parse_EOL;
604
605    ----------------
606    -- Parse_File --
607    ----------------
608
609    function Parse_File (Num : File_Num) return Boolean is
610       Chop_Name   : constant String_Access   := File.Table (Num).Name;
611       Save_Stdout : constant File_Descriptor := dup (Standout);
612       Offset_Name : Temp_File_Name;
613       Offset_FD   : File_Descriptor;
614       Buffer      : String_Access;
615       Success     : Boolean;
616       Failure     : exception;
617
618    begin
619       --  Display copy of GNAT command if verbose mode
620
621       if Verbose_Mode then
622          Put (Gnat_Cmd.all);
623
624          for J in 1 .. Gnat_Args'Length loop
625             Put (' ');
626             Put (Gnat_Args (J).all);
627          end loop;
628
629          Put (' ');
630          Put_Line (Chop_Name.all);
631       end if;
632
633       --  Create temporary file
634
635       Create_Temp_File (Offset_FD, Offset_Name);
636
637       if Offset_FD = Invalid_FD then
638          Error_Msg ("gnatchop: cannot create temporary file");
639          Close (Save_Stdout);
640          return False;
641       end if;
642
643       --  Redirect Stdout to this temporary file in the Unix way
644
645       if dup2 (Offset_FD, Standout) = Invalid_FD then
646          Error_Msg ("gnatchop: cannot redirect stdout to temporary file");
647          Close (Save_Stdout);
648          Close (Offset_FD);
649          return False;
650       end if;
651
652       --  Call Gnat on the source filename argument with special options
653       --  to generate offset information. If this special compilation completes
654       --  successfully then we can do the actual gnatchop operation.
655
656       Spawn (Gnat_Cmd.all, Gnat_Args.all & Chop_Name, Success);
657
658       if not Success then
659          Error_Msg (Chop_Name.all & ": parse errors detected");
660          Error_Msg (Chop_Name.all & ": chop may not be successful");
661       end if;
662
663       --  Restore stdout
664
665       if dup2 (Save_Stdout, Standout) = Invalid_FD then
666          Error_Msg ("gnatchop: cannot restore stdout");
667       end if;
668
669       --  Reopen the file to start reading from the beginning
670
671       Close (Offset_FD);
672       Close (Save_Stdout);
673       Offset_FD := Open_Read (Offset_Name'Address, Binary);
674
675       if Offset_FD = Invalid_FD then
676          Error_Msg ("gnatchop: cannot access offset info");
677          raise Failure;
678       end if;
679
680       Read_File (Offset_FD, Buffer, Success);
681
682       if not Success then
683          Error_Msg ("gnatchop: error reading offset info");
684          Close (Offset_FD);
685          raise Failure;
686       else
687          Parse_Offset_Info (Num, Buffer);
688       end if;
689
690       --  Close and delete temporary file
691
692       Close (Offset_FD);
693       Delete_File (Offset_Name'Address, Success);
694
695       return Success;
696
697    exception
698       when Failure | Types.Terminate_Program =>
699          Close (Offset_FD);
700          Delete_File (Offset_Name'Address, Success);
701          return False;
702
703    end Parse_File;
704
705    -----------------------
706    -- Parse_Offset_Info --
707    -----------------------
708
709    procedure Parse_Offset_Info
710      (Chop_File : File_Num;
711       Source    : not null access String)
712    is
713       First_Unit : constant Unit_Num := Unit.Last + 1;
714       Bufferg    : String_Access     := null;
715       Parse_Ptr  : File_Offset       := Source'First;
716       Token_Ptr  : File_Offset;
717       Info       : Unit_Info;
718
719       function Match (Literal : String) return Boolean;
720       --  Checks if given string appears at the current Token_Ptr location
721       --  and if so, bumps Parse_Ptr past the token and returns True. If
722       --  the string is not present, sets Parse_Ptr to Token_Ptr and
723       --  returns False.
724
725       -----------
726       -- Match --
727       -----------
728
729       function Match (Literal : String) return Boolean is
730       begin
731          Parse_Token (Source, Parse_Ptr, Token_Ptr);
732
733          if Source'Last  + 1 - Token_Ptr < Literal'Length
734            or else
735              Source (Token_Ptr .. Token_Ptr + Literal'Length - 1) /= Literal
736          then
737             Parse_Ptr := Token_Ptr;
738             return False;
739          end if;
740
741          Parse_Ptr := Token_Ptr + Literal'Length;
742          return True;
743       end Match;
744
745    --  Start of processing for Parse_Offset_Info
746
747    begin
748       loop
749          --  Set default values, should get changed for all
750          --  units/pragmas except for the last
751
752          Info.Chop_File := Chop_File;
753          Info.Length := 0;
754
755          --  Parse the current line of offset information into Info
756          --  and exit the loop if there are any errors or on EOF.
757
758          --  First case, parse a line in the following format:
759
760          --  Unit x (spec) line 7, file offset 142, [SR, ]file name x.ads
761
762          --  Note that the unit name can be an operator name in quotes.
763          --  This is of course illegal, but both GNAT and gnatchop handle
764          --  the case so that this error does not intefere with chopping.
765
766          --  The SR ir present indicates that a source reference pragma
767          --  was processed as part of this unit (and that therefore no
768          --  Source_Reference pragma should be generated.
769
770          if Match ("Unit") then
771             Parse_Token (Source, Parse_Ptr, Token_Ptr);
772
773             if Match ("(body)") then
774                Info.Kind := Unit_Body;
775             elsif Match ("(spec)") then
776                Info.Kind := Unit_Spec;
777             else
778                exit;
779             end if;
780
781             exit when not Match ("line");
782             Parse_Token (Source, Parse_Ptr, Token_Ptr);
783             Info.Start_Line := Line_Num'Value
784               (Source (Token_Ptr .. Parse_Ptr - 1));
785
786             exit when not Match ("file offset");
787             Parse_Token (Source, Parse_Ptr, Token_Ptr);
788             Info.Offset := File_Offset'Value
789               (Source (Token_Ptr .. Parse_Ptr - 1));
790
791             Info.SR_Present := Match ("SR, ");
792
793             exit when not Match ("file name");
794             Parse_Token (Source, Parse_Ptr, Token_Ptr);
795             Info.File_Name := new String'
796               (Directory.all & Source (Token_Ptr .. Parse_Ptr - 1));
797             Parse_EOL (Source, Parse_Ptr);
798
799          --  Second case, parse a line of the following form
800
801          --  Configuration pragmas at line 10, file offset 223
802
803          elsif Match ("Configuration pragmas at") then
804             Info.Kind := Config_Pragmas;
805             Info.File_Name := Config_File_Name;
806
807             exit when not Match ("line");
808             Parse_Token (Source, Parse_Ptr, Token_Ptr);
809             Info.Start_Line := Line_Num'Value
810               (Source (Token_Ptr .. Parse_Ptr - 1));
811
812             exit when not Match ("file offset");
813             Parse_Token (Source, Parse_Ptr, Token_Ptr);
814             Info.Offset := File_Offset'Value
815               (Source (Token_Ptr .. Parse_Ptr - 1));
816
817             Parse_EOL (Source, Parse_Ptr);
818
819          --  Third case, parse a line of the following form
820
821          --    Source_Reference pragma for file "filename"
822
823          --  This appears at the start of the file only, and indicates
824          --  the name to be used on any generated Source_Reference pragmas.
825
826          elsif Match ("Source_Reference pragma for file ") then
827             Parse_Token (Source, Parse_Ptr, Token_Ptr);
828             File.Table (Chop_File).SR_Name :=
829               new String'(Source (Token_Ptr + 1 .. Parse_Ptr - 2));
830             Parse_EOL (Source, Parse_Ptr);
831             goto Continue;
832
833          --  Unrecognized keyword or end of file
834
835          else
836             exit;
837          end if;
838
839          --  Store the data in the Info record in the Unit.Table
840
841          Unit.Increment_Last;
842          Unit.Table (Unit.Last) := Info;
843
844          --  If this is not the first unit from the file, calculate
845          --  the length of the previous unit as difference of the offsets
846
847          if Unit.Last > First_Unit then
848             Unit.Table (Unit.Last - 1).Length :=
849               Info.Offset - Unit.Table (Unit.Last - 1).Offset;
850          end if;
851
852          --  If not in compilation mode combine current unit with any
853          --  preceding configuration pragmas.
854
855          if not Compilation_Mode
856            and then Unit.Last > First_Unit
857            and then Unit.Table (Unit.Last - 1).Kind = Config_Pragmas
858          then
859             Info.Start_Line := Unit.Table (Unit.Last - 1).Start_Line;
860             Info.Offset := Unit.Table (Unit.Last - 1).Offset;
861
862             --  Delete the configuration pragma entry
863
864             Unit.Table (Unit.Last - 1) := Info;
865             Unit.Decrement_Last;
866          end if;
867
868          --  If in compilation mode, and previous entry is the initial
869          --  entry for the file and is for configuration pragmas, then
870          --  they are to be appended to every unit in the file.
871
872          if Compilation_Mode
873            and then Unit.Last = First_Unit + 1
874            and then Unit.Table (First_Unit).Kind = Config_Pragmas
875          then
876             Bufferg :=
877               Get_Config_Pragmas
878                 (Unit.Table (Unit.Last - 1).Chop_File, First_Unit);
879             Unit.Table (Unit.Last - 1) := Info;
880             Unit.Decrement_Last;
881          end if;
882
883          Unit.Table (Unit.Last).Bufferg := Bufferg;
884
885          --  If in compilation mode, and this is not the first item,
886          --  combine configuration pragmas with previous unit, which
887          --  will cause an error message to be generated when the unit
888          --  is compiled.
889
890          if Compilation_Mode
891            and then Unit.Last > First_Unit
892            and then Unit.Table (Unit.Last).Kind = Config_Pragmas
893          then
894             Unit.Decrement_Last;
895          end if;
896
897       <<Continue>>
898          null;
899
900       end loop;
901
902       --  Find out if the loop was exited prematurely because of
903       --  an error or if the EOF marker was found.
904
905       if Source (Parse_Ptr) /= EOF then
906          Error_Msg
907            (File.Table (Chop_File).Name.all & ": error parsing offset info");
908          return;
909       end if;
910
911       --  Handle case of a chop file consisting only of config pragmas
912
913       if Unit.Last = First_Unit
914         and then Unit.Table (Unit.Last).Kind = Config_Pragmas
915       then
916          --  In compilation mode, we append such a file to gnat.adc
917
918          if Compilation_Mode then
919             Write_Config_File (Unit.Table (Unit.Last).Chop_File, First_Unit);
920             Unit.Decrement_Last;
921
922          --  In default (non-compilation) mode, this is invalid
923
924          else
925             Error_Msg
926               (File.Table (Chop_File).Name.all &
927                ": no units found (only pragmas)");
928             Unit.Decrement_Last;
929          end if;
930       end if;
931
932       --  Handle case of a chop file ending with config pragmas. This can
933       --  happen only in default non-compilation mode, since in compilation
934       --  mode such configuration pragmas are part of the preceding unit.
935       --  We simply concatenate such pragmas to the previous file which
936       --  will cause a compilation error, which is appropriate.
937
938       if Unit.Last > First_Unit
939         and then Unit.Table (Unit.Last).Kind = Config_Pragmas
940       then
941          Unit.Decrement_Last;
942       end if;
943    end Parse_Offset_Info;
944
945    -----------------
946    -- Parse_Token --
947    -----------------
948
949    procedure Parse_Token
950      (Source    : not null access String;
951       Ptr       : in out Positive;
952       Token_Ptr : out Positive)
953    is
954       In_Quotes : Boolean := False;
955
956    begin
957       --  Skip separators
958
959       while Source (Ptr) = ' ' or Source (Ptr) = ',' loop
960          Ptr := Ptr + 1;
961       end loop;
962
963       Token_Ptr := Ptr;
964
965       --  Find end-of-token
966
967       while (In_Quotes or else not (Source (Ptr) = ' ' or Source (Ptr) = ','))
968         and then Source (Ptr) >= ' '
969       loop
970          if Source (Ptr) = '"' then
971             In_Quotes := not In_Quotes;
972          end if;
973
974          Ptr := Ptr + 1;
975       end loop;
976    end Parse_Token;
977
978    ---------------
979    -- Read_File --
980    ---------------
981
982    procedure Read_File
983      (FD       : File_Descriptor;
984       Contents : out String_Access;
985       Success  : out Boolean)
986    is
987       Length      : constant File_Offset := File_Offset (File_Length (FD));
988       --  Include room for EOF char
989       Buffer      : constant String_Access := new String (1 .. Length + 1);
990
991       This_Read   : Integer;
992       Read_Ptr    : File_Offset := 1;
993
994    begin
995
996       loop
997          This_Read := Read (FD,
998            A => Buffer (Read_Ptr)'Address,
999            N => Length + 1 - Read_Ptr);
1000          Read_Ptr := Read_Ptr + Integer'Max (This_Read, 0);
1001          exit when This_Read <= 0;
1002       end loop;
1003
1004       Buffer (Read_Ptr) := EOF;
1005       Contents := new String (1 .. Read_Ptr);
1006       Contents.all := Buffer (1 .. Read_Ptr);
1007
1008       --  Things aren't simple on VMS due to the plethora of file types
1009       --  and organizations. It seems clear that there shouldn't be more
1010       --  bytes read than are contained in the file though.
1011
1012       if Hostparm.OpenVMS then
1013          Success := Read_Ptr <= Length + 1;
1014       else
1015          Success := Read_Ptr = Length + 1;
1016       end if;
1017    end Read_File;
1018
1019    ----------------------------
1020    -- Report_Duplicate_Units --
1021    ----------------------------
1022
1023    function Report_Duplicate_Units return Boolean is
1024       US : SUnit_Num;
1025       U  : Unit_Num;
1026
1027       Duplicates : Boolean  := False;
1028
1029    begin
1030       US := 1;
1031       while US < SUnit_Num (Unit.Last) loop
1032          U := Sorted_Units.Table (US);
1033
1034          if Is_Duplicated (US) then
1035             Duplicates := True;
1036
1037             --  Move to last two versions of duplicated file to make it clearer
1038             --  to understand which file is retained in case of overwriting.
1039
1040             while US + 1 < SUnit_Num (Unit.Last) loop
1041                exit when not Is_Duplicated (US + 1);
1042                US := US + 1;
1043             end loop;
1044
1045             U := Sorted_Units.Table (US);
1046
1047             if Overwrite_Files then
1048                Warning_Msg (Unit.Table (U).File_Name.all
1049                  & " is duplicated (all but last will be skipped)");
1050
1051             elsif Unit.Table (U).Chop_File =
1052                     Unit.Table (Sorted_Units.Table (US + 1)).Chop_File
1053             then
1054                Error_Msg (Unit.Table (U).File_Name.all
1055                  & " is duplicated in "
1056                  & File.Table (Unit.Table (U).Chop_File).Name.all);
1057
1058             else
1059                Error_Msg (Unit.Table (U).File_Name.all
1060                   & " in "
1061                   & File.Table (Unit.Table (U).Chop_File).Name.all
1062                   & " is duplicated in "
1063                   & File.Table
1064                       (Unit.Table
1065                         (Sorted_Units.Table (US + 1)).Chop_File).Name.all);
1066             end if;
1067          end if;
1068
1069          US := US + 1;
1070       end loop;
1071
1072       if Duplicates and not Overwrite_Files then
1073          if Hostparm.OpenVMS then
1074             Put_Line
1075               ("use /OVERWRITE to overwrite files and keep last version");
1076          else
1077             Put_Line ("use -w to overwrite files and keep last version");
1078          end if;
1079       end if;
1080
1081       return Duplicates;
1082    end Report_Duplicate_Units;
1083
1084    --------------------
1085    -- Scan_Arguments --
1086    --------------------
1087
1088    function Scan_Arguments return Boolean is
1089       Kset : Boolean := False;
1090       --  Set true if -k switch found
1091
1092    begin
1093       Initialize_Option_Scan;
1094
1095       --  Scan options first
1096
1097       loop
1098          case Getopt ("c gnat? h k? p q r v w x -GCC=!") is
1099             when ASCII.NUL =>
1100                exit;
1101
1102             when '-' =>
1103                Gcc     := new String'(Parameter);
1104                Gcc_Set := True;
1105
1106             when 'c' =>
1107                Compilation_Mode := True;
1108
1109             when 'g' =>
1110                Gnat_Args :=
1111                  new Argument_List'(Gnat_Args.all &
1112                                       new String'("-gnat" & Parameter));
1113
1114             when 'h' =>
1115                Usage;
1116                raise Types.Terminate_Program;
1117
1118             when 'k' =>
1119                declare
1120                   Param : String_Access := new String'(Parameter);
1121
1122                begin
1123                   if Param.all /= "" then
1124                      for J in Param'Range loop
1125                         if Param (J) not in '0' .. '9' then
1126                            if Hostparm.OpenVMS then
1127                               Error_Msg ("/FILE_NAME_MAX_LENGTH=nnn" &
1128                                          " requires numeric parameter");
1129                            else
1130                               Error_Msg ("-k# requires numeric parameter");
1131                            end if;
1132
1133                            return False;
1134                         end if;
1135                      end loop;
1136
1137                   else
1138                      if Hostparm.OpenVMS then
1139                         Param := new String'("39");
1140                      else
1141                         Param := new String'("8");
1142                      end if;
1143                   end if;
1144
1145                   Gnat_Args :=
1146                     new Argument_List'(Gnat_Args.all &
1147                                          new String'("-gnatk" & Param.all));
1148                   Kset := True;
1149                end;
1150
1151             when 'p' =>
1152                Preserve_Mode := True;
1153
1154             when 'q' =>
1155                Quiet_Mode := True;
1156
1157             when 'r' =>
1158                Source_References := True;
1159
1160             when 'v' =>
1161                Verbose_Mode := True;
1162                Display_Version ("GNATCHOP", "1998");
1163
1164             when 'w' =>
1165                Overwrite_Files := True;
1166
1167             when 'x' =>
1168                Exit_On_Error := True;
1169
1170             when others =>
1171                null;
1172          end case;
1173       end loop;
1174
1175       if not Kset and then Maximum_File_Name_Length > 0 then
1176
1177          --  If this system has restricted filename lengths, tell gnat1
1178          --  about them, removing the leading blank from the image string.
1179
1180          Gnat_Args :=
1181            new Argument_List'(Gnat_Args.all
1182              & new String'("-gnatk"
1183                & Maximum_File_Name_Length_String
1184                  (Maximum_File_Name_Length_String'First + 1
1185                   .. Maximum_File_Name_Length_String'Last)));
1186       end if;
1187
1188       --  Scan file names
1189
1190       loop
1191          declare
1192             S : constant String := Get_Argument (Do_Expansion => True);
1193
1194          begin
1195             exit when S = "";
1196             File.Increment_Last;
1197             File.Table (File.Last).Name    := new String'(S);
1198             File.Table (File.Last).SR_Name := null;
1199          end;
1200       end loop;
1201
1202       --  Case of more than one file where last file is a directory
1203
1204       if File.Last > 1
1205         and then Is_Directory (File.Table (File.Last).Name.all)
1206       then
1207          Directory := File.Table (File.Last).Name;
1208          File.Decrement_Last;
1209
1210          --  Make sure Directory is terminated with a directory separator,
1211          --  so we can generate the output by just appending a filename.
1212
1213          if Directory (Directory'Last) /= Directory_Separator
1214             and then Directory (Directory'Last) /= '/'
1215          then
1216             Directory := new String'(Directory.all & Directory_Separator);
1217          end if;
1218
1219       --  At least one filename must be given
1220
1221       elsif File.Last = 0 then
1222          Usage;
1223          return False;
1224
1225       --  No directory given, set directory to null, so that we can just
1226       --  concatenate the directory name to the file name unconditionally.
1227
1228       else
1229          Directory := new String'("");
1230       end if;
1231
1232       --  Finally check all filename arguments
1233
1234       for File_Num in 1 .. File.Last loop
1235          declare
1236             F : constant String := File.Table (File_Num).Name.all;
1237
1238          begin
1239
1240             if Is_Directory (F) then
1241                Error_Msg (F & " is a directory, cannot be chopped");
1242                return False;
1243
1244             elsif not Is_Regular_File (F) then
1245                Error_Msg (F & " not found");
1246                return False;
1247             end if;
1248          end;
1249       end loop;
1250
1251       return True;
1252
1253    exception
1254       when Invalid_Switch =>
1255          Error_Msg ("invalid switch " & Full_Switch);
1256          return False;
1257
1258       when Invalid_Parameter =>
1259          if Hostparm.OpenVMS then
1260             Error_Msg ("/FILE_NAME_MAX_LENGTH=nnn qualifier" &
1261                        " requires numeric parameter");
1262          else
1263             Error_Msg ("-k switch requires numeric parameter");
1264          end if;
1265
1266          return False;
1267
1268    end Scan_Arguments;
1269
1270    ----------------
1271    -- Sort_Units --
1272    ----------------
1273
1274    procedure Sort_Units is
1275
1276       procedure Move (From : Natural; To : Natural);
1277       --  Procedure used to sort the unit list
1278       --  Unit.Table (To) := Unit_List (From); used by sort
1279
1280       function Lt (Left, Right : Natural) return Boolean;
1281       --  Compares Left and Right units based on file name (first),
1282       --  Chop_File (second) and Offset (third). This ordering is
1283       --  important to keep the last version in case of duplicate files.
1284
1285       package Unit_Sort is new GNAT.Heap_Sort_G (Move, Lt);
1286       --  Used for sorting on filename to detect duplicates
1287
1288       --------
1289       -- Lt --
1290       --------
1291
1292       function Lt (Left, Right : Natural) return Boolean is
1293          L : Unit_Info renames
1294                Unit.Table (Sorted_Units.Table (SUnit_Num (Left)));
1295
1296          R : Unit_Info renames
1297                Unit.Table (Sorted_Units.Table (SUnit_Num (Right)));
1298
1299       begin
1300          return L.File_Name.all < R.File_Name.all
1301            or else (L.File_Name.all = R.File_Name.all
1302                      and then (L.Chop_File < R.Chop_File
1303                                  or else (L.Chop_File = R.Chop_File
1304                                             and then L.Offset < R.Offset)));
1305       end Lt;
1306
1307       ----------
1308       -- Move --
1309       ----------
1310
1311       procedure Move (From : Natural; To : Natural) is
1312       begin
1313          Sorted_Units.Table (SUnit_Num (To)) :=
1314            Sorted_Units.Table (SUnit_Num (From));
1315       end Move;
1316
1317    --  Start of processing for Sort_Units
1318
1319    begin
1320       Sorted_Units.Set_Last (SUnit_Num (Unit.Last));
1321
1322       for J in 1 .. Unit.Last loop
1323          Sorted_Units.Table (SUnit_Num (J)) := J;
1324       end loop;
1325
1326       --  Sort Unit.Table, using Sorted_Units.Table (0) as scratch
1327
1328       Unit_Sort.Sort (Natural (Unit.Last));
1329
1330       --  Set the Sorted_Index fields in the unit tables
1331
1332       for J in 1 .. SUnit_Num (Unit.Last) loop
1333          Unit.Table (Sorted_Units.Table (J)).Sorted_Index := J;
1334       end loop;
1335    end Sort_Units;
1336
1337    -----------
1338    -- Usage --
1339    -----------
1340
1341    procedure Usage is
1342    begin
1343       Put_Line
1344         ("Usage: gnatchop [-c] [-h] [-k#] " &
1345          "[-r] [-p] [-q] [-v] [-w] [-x] [--GCC=xx] file [file ...] [dir]");
1346
1347       New_Line;
1348       Put_Line
1349         ("  -c       compilation mode, configuration pragmas " &
1350          "follow RM rules");
1351
1352       Put_Line
1353         ("  -gnatxxx passes the -gnatxxx switch to gnat parser");
1354
1355       Put_Line
1356         ("  -h       help: output this usage information");
1357
1358       Put_Line
1359         ("  -k#      krunch file names of generated files to " &
1360          "no more than # characters");
1361
1362       Put_Line
1363         ("  -k       krunch file names of generated files to " &
1364          "no more than 8 characters");
1365
1366       Put_Line
1367         ("  -p       preserve time stamp, output files will " &
1368          "have same stamp as input");
1369
1370       Put_Line
1371         ("  -q       quiet mode, no output of generated file " &
1372          "names");
1373
1374       Put_Line
1375         ("  -r       generate Source_Reference pragmas refer" &
1376          "encing original source file");
1377
1378       Put_Line
1379         ("  -v       verbose mode, output version and generat" &
1380          "ed commands");
1381
1382       Put_Line
1383         ("  -w       overwrite existing filenames");
1384
1385       Put_Line
1386         ("  -x       exit on error");
1387
1388       Put_Line
1389         ("  --GCC=xx specify the path of the gnat parser to be used");
1390
1391       New_Line;
1392       Put_Line
1393         ("  file...  list of source files to be chopped");
1394
1395       Put_Line
1396         ("  dir      directory location for split files (defa" &
1397          "ult = current directory)");
1398    end Usage;
1399
1400    -----------------
1401    -- Warning_Msg --
1402    -----------------
1403
1404    procedure Warning_Msg (Message : String) is
1405    begin
1406       Warning_Count := Warning_Count + 1;
1407       Put_Line (Standard_Error, "warning: " & Message);
1408    end Warning_Msg;
1409
1410    -------------------------
1411    -- Write_Chopped_Files --
1412    -------------------------
1413
1414    function Write_Chopped_Files (Input : File_Num) return Boolean is
1415       Name    : aliased constant String :=
1416                   File.Table (Input).Name.all & ASCII.Nul;
1417       FD      : File_Descriptor;
1418       Buffer  : String_Access;
1419       Success : Boolean;
1420       TS_Time : OS_Time;
1421
1422    begin
1423       FD := Open_Read (Name'Address, Binary);
1424       TS_Time := File_Time_Stamp (FD);
1425
1426       if FD = Invalid_FD then
1427          Error_Msg ("cannot open " & File.Table (Input).Name.all);
1428          return False;
1429       end if;
1430
1431       Read_File (FD, Buffer, Success);
1432
1433       if not Success then
1434          Error_Msg ("cannot read " & File.Table (Input).Name.all);
1435          Close (FD);
1436          return False;
1437       end if;
1438
1439       if not Quiet_Mode then
1440          Put_Line ("splitting " & File.Table (Input).Name.all & " into:");
1441       end if;
1442
1443       --  Only chop those units that come from this file
1444
1445       for Num in 1 .. Unit.Last loop
1446          if Unit.Table (Num).Chop_File = Input then
1447             Write_Unit (Buffer, Num, TS_Time, Success);
1448             exit when not Success;
1449          end if;
1450       end loop;
1451
1452       Close (FD);
1453       return Success;
1454    end Write_Chopped_Files;
1455
1456    -----------------------
1457    -- Write_Config_File --
1458    -----------------------
1459
1460    procedure Write_Config_File (Input : File_Num; U : Unit_Num) is
1461       FD      : File_Descriptor;
1462       Name    : aliased constant String := "gnat.adc" & ASCII.NUL;
1463       Buffer  : String_Access;
1464       Success : Boolean;
1465       Append  : Boolean;
1466       Buffera : String_Access;
1467       Bufferl : Natural;
1468
1469    begin
1470       Write_gnat_adc := True;
1471       FD := Open_Read_Write (Name'Address, Binary);
1472
1473       if FD = Invalid_FD then
1474          FD := Create_File (Name'Address, Binary);
1475          Append := False;
1476
1477          if not Quiet_Mode then
1478             Put_Line ("writing configuration pragmas from " &
1479                File.Table (Input).Name.all & " to gnat.adc");
1480          end if;
1481
1482       else
1483          Append := True;
1484
1485          if not Quiet_Mode then
1486             Put_Line
1487               ("appending configuration pragmas from " &
1488                File.Table (Input).Name.all & " to gnat.adc");
1489          end if;
1490       end if;
1491
1492       Success := FD /= Invalid_FD;
1493
1494       if not Success then
1495          Error_Msg ("cannot create gnat.adc");
1496          return;
1497       end if;
1498
1499       --  In append mode, acquire existing gnat.adc file
1500
1501       if Append then
1502          Read_File (FD, Buffera, Success);
1503
1504          if not Success then
1505             Error_Msg ("cannot read gnat.adc");
1506             return;
1507          end if;
1508
1509          --  Find location of EOF byte if any to exclude from append
1510
1511          Bufferl := 1;
1512          while Bufferl <= Buffera'Last
1513            and then Buffera (Bufferl) /= EOF
1514          loop
1515             Bufferl := Bufferl + 1;
1516          end loop;
1517
1518          Bufferl := Bufferl - 1;
1519          Close (FD);
1520
1521          --  Write existing gnat.adc to new gnat.adc file
1522
1523          FD := Create_File (Name'Address, Binary);
1524          Success := Write (FD, Buffera (1)'Address, Bufferl) = Bufferl;
1525
1526          if not Success then
1527             Error_Msg ("error writing gnat.adc");
1528             return;
1529          end if;
1530       end if;
1531
1532       Buffer := Get_Config_Pragmas  (Input, U);
1533
1534       if Buffer /= null then
1535          Success := Write (FD, Buffer.all'Address, Buffer'Length) =
1536                                  Buffer'Length;
1537
1538          if not Success then
1539             Error_Msg ("disk full writing gnat.adc");
1540             return;
1541          end if;
1542       end if;
1543
1544       Close (FD);
1545    end Write_Config_File;
1546
1547    -----------------------------------
1548    -- Write_Source_Reference_Pragma --
1549    -----------------------------------
1550
1551    procedure Write_Source_Reference_Pragma
1552      (Info    : Unit_Info;
1553       Line    : Line_Num;
1554       File    : Stream_IO.File_Type;
1555       EOL     : EOL_String;
1556       Success : in out Boolean)
1557    is
1558       FTE : File_Entry renames Gnatchop.File.Table (Info.Chop_File);
1559       Nam : String_Access;
1560
1561    begin
1562       if Success and Source_References and not Info.SR_Present then
1563          if FTE.SR_Name /= null then
1564             Nam := FTE.SR_Name;
1565          else
1566             Nam := FTE.Name;
1567          end if;
1568
1569          declare
1570             Reference : String :=
1571                           "pragma Source_Reference (000000, """
1572                             & Nam.all & """);" & EOL.Str;
1573
1574             Pos : Positive := Reference'First;
1575             Lin : Line_Num := Line;
1576
1577          begin
1578             while Reference (Pos + 1) /= ',' loop
1579                Pos := Pos + 1;
1580             end loop;
1581
1582             while Reference (Pos) = '0' loop
1583                Reference (Pos) := Character'Val
1584                  (Character'Pos ('0') + Lin mod 10);
1585                Lin := Lin / 10;
1586                Pos := Pos - 1;
1587             end loop;
1588
1589             --  Assume there are enough zeroes for any program length
1590
1591             pragma Assert (Lin = 0);
1592
1593             begin
1594                String'Write (Stream_IO.Stream (File), Reference);
1595                Success := True;
1596             exception
1597                when others =>
1598                   Success := False;
1599             end;
1600          end;
1601       end if;
1602    end Write_Source_Reference_Pragma;
1603
1604    ----------------
1605    -- Write_Unit --
1606    ----------------
1607
1608    procedure Write_Unit
1609      (Source  : not null access String;
1610       Num     : Unit_Num;
1611       TS_Time : OS_Time;
1612       Success : out Boolean)
1613    is
1614
1615       procedure OS_Filename
1616         (Name     : String;
1617          W_Name   : Wide_String;
1618          OS_Name  : Address;
1619          N_Length : access Natural;
1620          Encoding : Address;
1621          E_Length : access Natural);
1622       pragma Import (C, OS_Filename, "__gnat_os_filename");
1623       --  Returns in OS_Name the proper name for the OS when used with the
1624       --  returned Encoding value. For example on Windows this will return the
1625       --  UTF-8 encoded name into OS_Name and set Encoding to encoding=utf8
1626       --  (form parameter Stream_IO).
1627       --  Name is the filename and W_Name the same filename in Unicode 16 bits
1628       --  (this corresponds to Win32 Unicode ISO/IEC 10646). N_Length and
1629       --  E_Length are the length returned in OS_Name and Encoding
1630       --  respectively.
1631
1632       Info     : Unit_Info renames Unit.Table (Num);
1633       Name     : aliased constant String := Info.File_Name.all & ASCII.NUL;
1634       W_Name   : aliased constant Wide_String := To_Wide_String (Name);
1635       EOL      : constant EOL_String :=
1636                    Get_EOL (Source, Source'First + Info.Offset);
1637
1638       OS_Name  : aliased String (1 .. Name'Length * 2);
1639       O_Length : aliased Natural := OS_Name'Length;
1640       Encoding : aliased String (1 .. 64);
1641       E_Length : aliased Natural := Encoding'Length;
1642
1643       Length   : File_Offset;
1644
1645    begin
1646       --  Skip duplicated files
1647
1648       if Is_Duplicated (Info.Sorted_Index) then
1649          Put_Line ("   " & Info.File_Name.all & " skipped");
1650          Success := Overwrite_Files;
1651          return;
1652       end if;
1653
1654       --  Get OS filename
1655
1656       OS_Filename
1657         (Name, W_Name,
1658          OS_Name'Address, O_Length'Access,
1659          Encoding'Address, E_Length'Access);
1660
1661       declare
1662          E_Name      : constant String := OS_Name (1 .. O_Length);
1663          C_Name      : aliased constant String := E_Name & ASCII.Nul;
1664          OS_Encoding : constant String := Encoding (1 .. E_Length);
1665          File        : Stream_IO.File_Type;
1666       begin
1667          begin
1668             if not Overwrite_Files and then Exists (E_Name) then
1669                raise Stream_IO.Name_Error;
1670             else
1671                Stream_IO.Create
1672                  (File, Stream_IO.Out_File, E_Name, OS_Encoding);
1673                Success := True;
1674             end if;
1675          exception
1676             when Stream_IO.Name_Error | Stream_IO.Use_Error =>
1677                Error_Msg ("cannot create " & Info.File_Name.all);
1678                return;
1679          end;
1680
1681          --  A length of 0 indicates that the rest of the file belongs to
1682          --  this unit. The actual length must be calculated now. Take into
1683          --  account that the last character (EOF) must not be written.
1684
1685          if Info.Length = 0 then
1686             Length := Source'Last - (Source'First + Info.Offset);
1687          else
1688             Length := Info.Length;
1689          end if;
1690
1691          --  Prepend configuration pragmas if necessary
1692
1693          if Success and then Info.Bufferg /= null then
1694             Write_Source_Reference_Pragma (Info, 1, File, EOL, Success);
1695
1696             String'Write (Stream_IO.Stream (File), Info.Bufferg.all);
1697          end if;
1698
1699          Write_Source_Reference_Pragma
1700            (Info, Info.Start_Line, File, EOL, Success);
1701
1702          if Success then
1703             begin
1704                String'Write
1705                  (Stream_IO.Stream (File),
1706                   Source (Source'First + Info.Offset ..
1707                       Source'First + Info.Offset + Length - 1));
1708             exception
1709                when Stream_IO.Use_Error | Stream_IO.Device_Error =>
1710                   Error_Msg ("disk full writing " & Info.File_Name.all);
1711                   return;
1712             end;
1713          end if;
1714
1715          if not Quiet_Mode then
1716             Put_Line ("   " & Info.File_Name.all);
1717          end if;
1718
1719          Stream_IO.Close (File);
1720
1721          if Preserve_Mode then
1722             File_Time_Stamp (C_Name'Address, TS_Time);
1723          end if;
1724       end;
1725    end Write_Unit;
1726
1727       procedure Check_Version_And_Help is new Check_Version_And_Help_G (Usage);
1728
1729 --  Start of processing for gnatchop
1730
1731 begin
1732    --  Add the directory where gnatchop is invoked in front of the
1733    --  path, if gnatchop is invoked with directory information.
1734    --  Only do this if the platform is not VMS, where the notion of path
1735    --  does not really exist.
1736
1737    if not Hostparm.OpenVMS then
1738       declare
1739          Command : constant String := Command_Name;
1740
1741       begin
1742          for Index in reverse Command'Range loop
1743             if Command (Index) = Directory_Separator then
1744                declare
1745                   Absolute_Dir : constant String :=
1746                                    Normalize_Pathname
1747                                      (Command (Command'First .. Index));
1748
1749                   PATH         : constant String :=
1750                                    Absolute_Dir &
1751                   Path_Separator &
1752                   Getenv ("PATH").all;
1753
1754                begin
1755                   Setenv ("PATH", PATH);
1756                end;
1757
1758                exit;
1759             end if;
1760          end loop;
1761       end;
1762    end if;
1763
1764    --  Process command line options and initialize global variables
1765
1766    --  First, scan to detect --version and/or --help
1767
1768    Check_Version_And_Help ("GNATCHOP", "1998");
1769
1770    if not Scan_Arguments then
1771       Set_Exit_Status (Failure);
1772       return;
1773    end if;
1774
1775    --  Check presence of required executables
1776
1777    Gnat_Cmd := Locate_Executable (Gcc.all, not Gcc_Set);
1778
1779    if Gnat_Cmd = null then
1780       goto No_Files_Written;
1781    end if;
1782
1783    --  First parse all files and read offset information
1784
1785    for Num in 1 .. File.Last loop
1786       if not Parse_File (Num) then
1787          goto No_Files_Written;
1788       end if;
1789    end loop;
1790
1791    --  Check if any units have been found (assumes non-empty Unit.Table)
1792
1793    if Unit.Last = 0 then
1794       if not Write_gnat_adc then
1795          Error_Msg ("no compilation units found", Warning => True);
1796       end if;
1797
1798       goto No_Files_Written;
1799    end if;
1800
1801    Sort_Units;
1802
1803    --  Check if any duplicate files would be created. If so, emit
1804    --  a warning if Overwrite_Files is true, otherwise generate an error.
1805
1806    if Report_Duplicate_Units and then not Overwrite_Files then
1807       goto No_Files_Written;
1808    end if;
1809
1810    --  Check if any files exist, if so do not write anything
1811    --  Because all files have been parsed and checked already,
1812    --  there won't be any duplicates
1813
1814    if not Overwrite_Files and then Files_Exist then
1815       goto No_Files_Written;
1816    end if;
1817
1818    --  After this point, all source files are read in succession
1819    --  and chopped into their destination files.
1820
1821    --  As the Source_File_Name pragmas are handled as logical file 0,
1822    --  write it first.
1823
1824    for F in 1 .. File.Last loop
1825       if not Write_Chopped_Files (F) then
1826          Set_Exit_Status (Failure);
1827          return;
1828       end if;
1829    end loop;
1830
1831    if Warning_Count > 0 then
1832       declare
1833          Warnings_Msg : constant String := Warning_Count'Img & " warning(s)";
1834       begin
1835          Error_Msg (Warnings_Msg (2 .. Warnings_Msg'Last), Warning => True);
1836       end;
1837    end if;
1838
1839    return;
1840
1841 <<No_Files_Written>>
1842
1843    --  Special error exit for all situations where no files have
1844    --  been written.
1845
1846    if not Write_gnat_adc then
1847       Error_Msg ("no source files written", Warning => True);
1848    end if;
1849
1850    return;
1851
1852 exception
1853    when Types.Terminate_Program =>
1854       null;
1855
1856 end Gnatchop;