OSDN Git Service

2007-09-26 Thomas Quinot <quinot@adacore.com>
[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       Success : Boolean;
432       Result  : String_Access;
433
434    begin
435       FD := Open_Read (Name'Address, Binary);
436
437       if FD = Invalid_FD then
438          Error_Msg ("cannot open " & File.Table (Input).Name.all);
439          return null;
440       end if;
441
442       Read_File (FD, Buffer, Success);
443
444       --  A length of 0 indicates that the rest of the file belongs to
445       --  this unit. The actual length must be calculated now. Take into
446       --  account that the last character (EOF) must not be written.
447
448       if Info.Length = 0 then
449          Length := Buffer'Last - (Buffer'First + Info.Offset);
450       else
451          Length := Info.Length;
452       end if;
453
454       Result := new String'(Buffer (1 .. Length));
455       Close (FD);
456       return Result;
457    end Get_Config_Pragmas;
458
459    -------------
460    -- Get_EOL --
461    -------------
462
463    function Get_EOL
464      (Source : not null access String;
465       Start  : Positive)
466       return   EOL_String
467    is
468       Ptr   : Positive := Start;
469       First : Positive;
470       Last  : Natural;
471
472    begin
473       --  Skip to end of line
474
475       while Source (Ptr) /= ASCII.CR and then
476             Source (Ptr) /= ASCII.LF and then
477             Source (Ptr) /= EOF
478       loop
479          Ptr := Ptr + 1;
480       end loop;
481
482       Last  := Ptr;
483
484       if Source (Ptr) /= EOF then
485
486          --  Found CR or LF
487
488          First := Ptr;
489
490       else
491          First := Ptr + 1;
492       end if;
493
494       --  Recognize CR/LF or LF/CR combination
495
496       if (Source (Ptr + 1) = ASCII.CR or Source (Ptr + 1) = ASCII.LF)
497          and then Source (Ptr) /= Source (Ptr + 1)
498       then
499          Last := First + 1;
500       end if;
501
502       return (Len => Last + 1 - First, Str => Source (First .. Last));
503    end Get_EOL;
504
505    -------------------
506    -- Is_Duplicated --
507    -------------------
508
509    function Is_Duplicated (U : SUnit_Num) return Boolean is
510    begin
511       return U < SUnit_Num (Unit.Last)
512         and then
513           Unit.Table (Sorted_Units.Table (U)).File_Name.all =
514           Unit.Table (Sorted_Units.Table (U + 1)).File_Name.all;
515    end Is_Duplicated;
516
517    -----------------------
518    -- Locate_Executable --
519    -----------------------
520
521    function Locate_Executable
522      (Program_Name    : String;
523       Look_For_Prefix : Boolean := True) return String_Access
524    is
525       Current_Command : constant String := Normalize_Pathname (Command_Name);
526       End_Of_Prefix   : Natural;
527       Start_Of_Prefix : Positive;
528       Result          : String_Access;
529
530    begin
531       Start_Of_Prefix := Current_Command'First;
532       End_Of_Prefix   := Start_Of_Prefix - 1;
533
534       if Look_For_Prefix then
535
536          --  Find Start_Of_Prefix
537
538          for J in reverse Current_Command'Range loop
539             if Current_Command (J) = '/' or
540               Current_Command (J) = Directory_Separator or
541               Current_Command (J) = ':'
542             then
543                Start_Of_Prefix := J + 1;
544                exit;
545             end if;
546          end loop;
547
548          --  Find End_Of_Prefix
549
550          for J in reverse Start_Of_Prefix .. Current_Command'Last loop
551             if Current_Command (J) = '-' then
552                End_Of_Prefix := J;
553                exit;
554             end if;
555          end loop;
556       end if;
557
558       declare
559          Command : constant String :=
560                      Current_Command (Start_Of_Prefix .. End_Of_Prefix) &
561                                                                 Program_Name;
562       begin
563          Result := Locate_Exec_On_Path (Command);
564
565          if Result = null then
566             Error_Msg
567               (Command & ": installation problem, executable not found");
568          end if;
569       end;
570
571       return Result;
572    end Locate_Executable;
573
574    ---------------
575    -- Parse_EOL --
576    ---------------
577
578    procedure Parse_EOL
579      (Source : not null access String;
580       Ptr    : in out Positive) is
581    begin
582       --  Skip to end of line
583
584       while Source (Ptr) /= ASCII.CR and then Source (Ptr) /= ASCII.LF
585         and then Source (Ptr) /= EOF
586       loop
587          Ptr := Ptr + 1;
588       end loop;
589
590       if Source (Ptr) /= EOF then
591          Ptr := Ptr + 1;      -- skip CR or LF
592       end if;
593
594       --  Skip past CR/LF or LF/CR combination
595
596       if (Source (Ptr) = ASCII.CR or Source (Ptr) = ASCII.LF)
597          and then Source (Ptr) /= Source (Ptr - 1)
598       then
599          Ptr := Ptr + 1;
600       end if;
601    end Parse_EOL;
602
603    ----------------
604    -- Parse_File --
605    ----------------
606
607    function Parse_File (Num : File_Num) return Boolean is
608       Chop_Name   : constant String_Access   := File.Table (Num).Name;
609       Save_Stdout : constant File_Descriptor := dup (Standout);
610       Offset_Name : Temp_File_Name;
611       Offset_FD   : File_Descriptor;
612       Buffer      : String_Access;
613       Success     : Boolean;
614       Failure     : exception;
615
616    begin
617       --  Display copy of GNAT command if verbose mode
618
619       if Verbose_Mode then
620          Put (Gnat_Cmd.all);
621
622          for J in 1 .. Gnat_Args'Length loop
623             Put (' ');
624             Put (Gnat_Args (J).all);
625          end loop;
626
627          Put (' ');
628          Put_Line (Chop_Name.all);
629       end if;
630
631       --  Create temporary file
632
633       Create_Temp_File (Offset_FD, Offset_Name);
634
635       if Offset_FD = Invalid_FD then
636          Error_Msg ("gnatchop: cannot create temporary file");
637          Close (Save_Stdout);
638          return False;
639       end if;
640
641       --  Redirect Stdout to this temporary file in the Unix way
642
643       if dup2 (Offset_FD, Standout) = Invalid_FD then
644          Error_Msg ("gnatchop: cannot redirect stdout to temporary file");
645          Close (Save_Stdout);
646          Close (Offset_FD);
647          return False;
648       end if;
649
650       --  Call Gnat on the source filename argument with special options
651       --  to generate offset information. If this special compilation completes
652       --  successfully then we can do the actual gnatchop operation.
653
654       Spawn (Gnat_Cmd.all, Gnat_Args.all & Chop_Name, Success);
655
656       if not Success then
657          Error_Msg (Chop_Name.all & ": parse errors detected");
658          Error_Msg (Chop_Name.all & ": chop may not be successful");
659       end if;
660
661       --  Restore stdout
662
663       if dup2 (Save_Stdout, Standout) = Invalid_FD then
664          Error_Msg ("gnatchop: cannot restore stdout");
665       end if;
666
667       --  Reopen the file to start reading from the beginning
668
669       Close (Offset_FD);
670       Close (Save_Stdout);
671       Offset_FD := Open_Read (Offset_Name'Address, Binary);
672
673       if Offset_FD = Invalid_FD then
674          Error_Msg ("gnatchop: cannot access offset info");
675          raise Failure;
676       end if;
677
678       Read_File (Offset_FD, Buffer, Success);
679
680       if not Success then
681          Error_Msg ("gnatchop: error reading offset info");
682          Close (Offset_FD);
683          raise Failure;
684       else
685          Parse_Offset_Info (Num, Buffer);
686       end if;
687
688       --  Close and delete temporary file
689
690       Close (Offset_FD);
691       Delete_File (Offset_Name'Address, Success);
692
693       return Success;
694
695    exception
696       when Failure | Types.Terminate_Program =>
697          Close (Offset_FD);
698          Delete_File (Offset_Name'Address, Success);
699          return False;
700
701    end Parse_File;
702
703    -----------------------
704    -- Parse_Offset_Info --
705    -----------------------
706
707    procedure Parse_Offset_Info
708      (Chop_File : File_Num;
709       Source    : not null access String)
710    is
711       First_Unit : constant Unit_Num := Unit.Last + 1;
712       Bufferg    : String_Access     := null;
713       Parse_Ptr  : File_Offset       := Source'First;
714       Token_Ptr  : File_Offset;
715       Info       : Unit_Info;
716
717       function Match (Literal : String) return Boolean;
718       --  Checks if given string appears at the current Token_Ptr location
719       --  and if so, bumps Parse_Ptr past the token and returns True. If
720       --  the string is not present, sets Parse_Ptr to Token_Ptr and
721       --  returns False.
722
723       -----------
724       -- Match --
725       -----------
726
727       function Match (Literal : String) return Boolean is
728       begin
729          Parse_Token (Source, Parse_Ptr, Token_Ptr);
730
731          if Source'Last  + 1 - Token_Ptr < Literal'Length
732            or else
733              Source (Token_Ptr .. Token_Ptr + Literal'Length - 1) /= Literal
734          then
735             Parse_Ptr := Token_Ptr;
736             return False;
737          end if;
738
739          Parse_Ptr := Token_Ptr + Literal'Length;
740          return True;
741       end Match;
742
743    --  Start of processing for Parse_Offset_Info
744
745    begin
746       loop
747          --  Set default values, should get changed for all
748          --  units/pragmas except for the last
749
750          Info.Chop_File := Chop_File;
751          Info.Length := 0;
752
753          --  Parse the current line of offset information into Info
754          --  and exit the loop if there are any errors or on EOF.
755
756          --  First case, parse a line in the following format:
757
758          --  Unit x (spec) line 7, file offset 142, [SR, ]file name x.ads
759
760          --  Note that the unit name can be an operator name in quotes.
761          --  This is of course illegal, but both GNAT and gnatchop handle
762          --  the case so that this error does not intefere with chopping.
763
764          --  The SR ir present indicates that a source reference pragma
765          --  was processed as part of this unit (and that therefore no
766          --  Source_Reference pragma should be generated.
767
768          if Match ("Unit") then
769             Parse_Token (Source, Parse_Ptr, Token_Ptr);
770
771             if Match ("(body)") then
772                Info.Kind := Unit_Body;
773             elsif Match ("(spec)") then
774                Info.Kind := Unit_Spec;
775             else
776                exit;
777             end if;
778
779             exit when not Match ("line");
780             Parse_Token (Source, Parse_Ptr, Token_Ptr);
781             Info.Start_Line := Line_Num'Value
782               (Source (Token_Ptr .. Parse_Ptr - 1));
783
784             exit when not Match ("file offset");
785             Parse_Token (Source, Parse_Ptr, Token_Ptr);
786             Info.Offset := File_Offset'Value
787               (Source (Token_Ptr .. Parse_Ptr - 1));
788
789             Info.SR_Present := Match ("SR, ");
790
791             exit when not Match ("file name");
792             Parse_Token (Source, Parse_Ptr, Token_Ptr);
793             Info.File_Name := new String'
794               (Directory.all & Source (Token_Ptr .. Parse_Ptr - 1));
795             Parse_EOL (Source, Parse_Ptr);
796
797          --  Second case, parse a line of the following form
798
799          --  Configuration pragmas at line 10, file offset 223
800
801          elsif Match ("Configuration pragmas at") then
802             Info.Kind := Config_Pragmas;
803             Info.File_Name := Config_File_Name;
804
805             exit when not Match ("line");
806             Parse_Token (Source, Parse_Ptr, Token_Ptr);
807             Info.Start_Line := Line_Num'Value
808               (Source (Token_Ptr .. Parse_Ptr - 1));
809
810             exit when not Match ("file offset");
811             Parse_Token (Source, Parse_Ptr, Token_Ptr);
812             Info.Offset := File_Offset'Value
813               (Source (Token_Ptr .. Parse_Ptr - 1));
814
815             Parse_EOL (Source, Parse_Ptr);
816
817          --  Third case, parse a line of the following form
818
819          --    Source_Reference pragma for file "filename"
820
821          --  This appears at the start of the file only, and indicates
822          --  the name to be used on any generated Source_Reference pragmas.
823
824          elsif Match ("Source_Reference pragma for file ") then
825             Parse_Token (Source, Parse_Ptr, Token_Ptr);
826             File.Table (Chop_File).SR_Name :=
827               new String'(Source (Token_Ptr + 1 .. Parse_Ptr - 2));
828             Parse_EOL (Source, Parse_Ptr);
829             goto Continue;
830
831          --  Unrecognized keyword or end of file
832
833          else
834             exit;
835          end if;
836
837          --  Store the data in the Info record in the Unit.Table
838
839          Unit.Increment_Last;
840          Unit.Table (Unit.Last) := Info;
841
842          --  If this is not the first unit from the file, calculate
843          --  the length of the previous unit as difference of the offsets
844
845          if Unit.Last > First_Unit then
846             Unit.Table (Unit.Last - 1).Length :=
847               Info.Offset - Unit.Table (Unit.Last - 1).Offset;
848          end if;
849
850          --  If not in compilation mode combine current unit with any
851          --  preceding configuration pragmas.
852
853          if not Compilation_Mode
854            and then Unit.Last > First_Unit
855            and then Unit.Table (Unit.Last - 1).Kind = Config_Pragmas
856          then
857             Info.Start_Line := Unit.Table (Unit.Last - 1).Start_Line;
858             Info.Offset := Unit.Table (Unit.Last - 1).Offset;
859
860             --  Delete the configuration pragma entry
861
862             Unit.Table (Unit.Last - 1) := Info;
863             Unit.Decrement_Last;
864          end if;
865
866          --  If in compilation mode, and previous entry is the initial
867          --  entry for the file and is for configuration pragmas, then
868          --  they are to be appended to every unit in the file.
869
870          if Compilation_Mode
871            and then Unit.Last = First_Unit + 1
872            and then Unit.Table (First_Unit).Kind = Config_Pragmas
873          then
874             Bufferg :=
875               Get_Config_Pragmas
876                 (Unit.Table (Unit.Last - 1).Chop_File, First_Unit);
877             Unit.Table (Unit.Last - 1) := Info;
878             Unit.Decrement_Last;
879          end if;
880
881          Unit.Table (Unit.Last).Bufferg := Bufferg;
882
883          --  If in compilation mode, and this is not the first item,
884          --  combine configuration pragmas with previous unit, which
885          --  will cause an error message to be generated when the unit
886          --  is compiled.
887
888          if Compilation_Mode
889            and then Unit.Last > First_Unit
890            and then Unit.Table (Unit.Last).Kind = Config_Pragmas
891          then
892             Unit.Decrement_Last;
893          end if;
894
895       <<Continue>>
896          null;
897
898       end loop;
899
900       --  Find out if the loop was exited prematurely because of
901       --  an error or if the EOF marker was found.
902
903       if Source (Parse_Ptr) /= EOF then
904          Error_Msg
905            (File.Table (Chop_File).Name.all & ": error parsing offset info");
906          return;
907       end if;
908
909       --  Handle case of a chop file consisting only of config pragmas
910
911       if Unit.Last = First_Unit
912         and then Unit.Table (Unit.Last).Kind = Config_Pragmas
913       then
914          --  In compilation mode, we append such a file to gnat.adc
915
916          if Compilation_Mode then
917             Write_Config_File (Unit.Table (Unit.Last).Chop_File, First_Unit);
918             Unit.Decrement_Last;
919
920          --  In default (non-compilation) mode, this is invalid
921
922          else
923             Error_Msg
924               (File.Table (Chop_File).Name.all &
925                ": no units found (only pragmas)");
926             Unit.Decrement_Last;
927          end if;
928       end if;
929
930       --  Handle case of a chop file ending with config pragmas. This can
931       --  happen only in default non-compilation mode, since in compilation
932       --  mode such configuration pragmas are part of the preceding unit.
933       --  We simply concatenate such pragmas to the previous file which
934       --  will cause a compilation error, which is appropriate.
935
936       if Unit.Last > First_Unit
937         and then Unit.Table (Unit.Last).Kind = Config_Pragmas
938       then
939          Unit.Decrement_Last;
940       end if;
941    end Parse_Offset_Info;
942
943    -----------------
944    -- Parse_Token --
945    -----------------
946
947    procedure Parse_Token
948      (Source    : not null access String;
949       Ptr       : in out Positive;
950       Token_Ptr : out Positive)
951    is
952       In_Quotes : Boolean := False;
953
954    begin
955       --  Skip separators
956
957       while Source (Ptr) = ' ' or Source (Ptr) = ',' loop
958          Ptr := Ptr + 1;
959       end loop;
960
961       Token_Ptr := Ptr;
962
963       --  Find end-of-token
964
965       while (In_Quotes or else not (Source (Ptr) = ' ' or Source (Ptr) = ','))
966         and then Source (Ptr) >= ' '
967       loop
968          if Source (Ptr) = '"' then
969             In_Quotes := not In_Quotes;
970          end if;
971
972          Ptr := Ptr + 1;
973       end loop;
974    end Parse_Token;
975
976    ---------------
977    -- Read_File --
978    ---------------
979
980    procedure Read_File
981      (FD       : File_Descriptor;
982       Contents : out String_Access;
983       Success  : out Boolean)
984    is
985       Length      : constant File_Offset := File_Offset (File_Length (FD));
986       --  Include room for EOF char
987       Buffer      : constant String_Access := new String (1 .. Length + 1);
988
989       This_Read   : Integer;
990       Read_Ptr    : File_Offset := 1;
991
992    begin
993
994       loop
995          This_Read := Read (FD,
996            A => Buffer (Read_Ptr)'Address,
997            N => Length + 1 - Read_Ptr);
998          Read_Ptr := Read_Ptr + Integer'Max (This_Read, 0);
999          exit when This_Read <= 0;
1000       end loop;
1001
1002       Buffer (Read_Ptr) := EOF;
1003       Contents := new String (1 .. Read_Ptr);
1004       Contents.all := Buffer (1 .. Read_Ptr);
1005
1006       --  Things aren't simple on VMS due to the plethora of file types
1007       --  and organizations. It seems clear that there shouldn't be more
1008       --  bytes read than are contained in the file though.
1009
1010       if Hostparm.OpenVMS then
1011          Success := Read_Ptr <= Length + 1;
1012       else
1013          Success := Read_Ptr = Length + 1;
1014       end if;
1015    end Read_File;
1016
1017    ----------------------------
1018    -- Report_Duplicate_Units --
1019    ----------------------------
1020
1021    function Report_Duplicate_Units return Boolean is
1022       US : SUnit_Num;
1023       U  : Unit_Num;
1024
1025       Duplicates : Boolean  := False;
1026
1027    begin
1028       US := 1;
1029       while US < SUnit_Num (Unit.Last) loop
1030          U := Sorted_Units.Table (US);
1031
1032          if Is_Duplicated (US) then
1033             Duplicates := True;
1034
1035             --  Move to last two versions of duplicated file to make it clearer
1036             --  to understand which file is retained in case of overwriting.
1037
1038             while US + 1 < SUnit_Num (Unit.Last) loop
1039                exit when not Is_Duplicated (US + 1);
1040                US := US + 1;
1041             end loop;
1042
1043             U := Sorted_Units.Table (US);
1044
1045             if Overwrite_Files then
1046                Warning_Msg (Unit.Table (U).File_Name.all
1047                  & " is duplicated (all but last will be skipped)");
1048
1049             elsif Unit.Table (U).Chop_File =
1050                     Unit.Table (Sorted_Units.Table (US + 1)).Chop_File
1051             then
1052                Error_Msg (Unit.Table (U).File_Name.all
1053                  & " is duplicated in "
1054                  & File.Table (Unit.Table (U).Chop_File).Name.all);
1055
1056             else
1057                Error_Msg (Unit.Table (U).File_Name.all
1058                   & " in "
1059                   & File.Table (Unit.Table (U).Chop_File).Name.all
1060                   & " is duplicated in "
1061                   & File.Table
1062                       (Unit.Table
1063                         (Sorted_Units.Table (US + 1)).Chop_File).Name.all);
1064             end if;
1065          end if;
1066
1067          US := US + 1;
1068       end loop;
1069
1070       if Duplicates and not Overwrite_Files then
1071          if Hostparm.OpenVMS then
1072             Put_Line
1073               ("use /OVERWRITE to overwrite files and keep last version");
1074          else
1075             Put_Line ("use -w to overwrite files and keep last version");
1076          end if;
1077       end if;
1078
1079       return Duplicates;
1080    end Report_Duplicate_Units;
1081
1082    --------------------
1083    -- Scan_Arguments --
1084    --------------------
1085
1086    function Scan_Arguments return Boolean is
1087       Kset : Boolean := False;
1088       --  Set true if -k switch found
1089
1090    begin
1091       Initialize_Option_Scan;
1092
1093       --  Scan options first
1094
1095       loop
1096          case Getopt ("c gnat? h k? p q r v w x -GCC=!") is
1097             when ASCII.NUL =>
1098                exit;
1099
1100             when '-' =>
1101                Gcc     := new String'(Parameter);
1102                Gcc_Set := True;
1103
1104             when 'c' =>
1105                Compilation_Mode := True;
1106
1107             when 'g' =>
1108                Gnat_Args :=
1109                  new Argument_List'(Gnat_Args.all &
1110                                       new String'("-gnat" & Parameter));
1111
1112             when 'h' =>
1113                Usage;
1114                raise Types.Terminate_Program;
1115
1116             when 'k' =>
1117                declare
1118                   Param : String_Access := new String'(Parameter);
1119
1120                begin
1121                   if Param.all /= "" then
1122                      for J in Param'Range loop
1123                         if Param (J) not in '0' .. '9' then
1124                            if Hostparm.OpenVMS then
1125                               Error_Msg ("/FILE_NAME_MAX_LENGTH=nnn" &
1126                                          " requires numeric parameter");
1127                            else
1128                               Error_Msg ("-k# requires numeric parameter");
1129                            end if;
1130
1131                            return False;
1132                         end if;
1133                      end loop;
1134
1135                   else
1136                      if Hostparm.OpenVMS then
1137                         Param := new String'("39");
1138                      else
1139                         Param := new String'("8");
1140                      end if;
1141                   end if;
1142
1143                   Gnat_Args :=
1144                     new Argument_List'(Gnat_Args.all &
1145                                          new String'("-gnatk" & Param.all));
1146                   Kset := True;
1147                end;
1148
1149             when 'p' =>
1150                Preserve_Mode := True;
1151
1152             when 'q' =>
1153                Quiet_Mode := True;
1154
1155             when 'r' =>
1156                Source_References := True;
1157
1158             when 'v' =>
1159                Verbose_Mode := True;
1160                Display_Version ("GNATCHOP", "1998");
1161
1162             when 'w' =>
1163                Overwrite_Files := True;
1164
1165             when 'x' =>
1166                Exit_On_Error := True;
1167
1168             when others =>
1169                null;
1170          end case;
1171       end loop;
1172
1173       if not Kset and then Maximum_File_Name_Length > 0 then
1174
1175          --  If this system has restricted filename lengths, tell gnat1
1176          --  about them, removing the leading blank from the image string.
1177
1178          Gnat_Args :=
1179            new Argument_List'(Gnat_Args.all
1180              & new String'("-gnatk"
1181                & Maximum_File_Name_Length_String
1182                  (Maximum_File_Name_Length_String'First + 1
1183                   .. Maximum_File_Name_Length_String'Last)));
1184       end if;
1185
1186       --  Scan file names
1187
1188       loop
1189          declare
1190             S : constant String := Get_Argument (Do_Expansion => True);
1191
1192          begin
1193             exit when S = "";
1194             File.Increment_Last;
1195             File.Table (File.Last).Name    := new String'(S);
1196             File.Table (File.Last).SR_Name := null;
1197          end;
1198       end loop;
1199
1200       --  Case of more than one file where last file is a directory
1201
1202       if File.Last > 1
1203         and then Is_Directory (File.Table (File.Last).Name.all)
1204       then
1205          Directory := File.Table (File.Last).Name;
1206          File.Decrement_Last;
1207
1208          --  Make sure Directory is terminated with a directory separator,
1209          --  so we can generate the output by just appending a filename.
1210
1211          if Directory (Directory'Last) /= Directory_Separator
1212             and then Directory (Directory'Last) /= '/'
1213          then
1214             Directory := new String'(Directory.all & Directory_Separator);
1215          end if;
1216
1217       --  At least one filename must be given
1218
1219       elsif File.Last = 0 then
1220          Usage;
1221          return False;
1222
1223       --  No directory given, set directory to null, so that we can just
1224       --  concatenate the directory name to the file name unconditionally.
1225
1226       else
1227          Directory := new String'("");
1228       end if;
1229
1230       --  Finally check all filename arguments
1231
1232       for File_Num in 1 .. File.Last loop
1233          declare
1234             F : constant String := File.Table (File_Num).Name.all;
1235
1236          begin
1237
1238             if Is_Directory (F) then
1239                Error_Msg (F & " is a directory, cannot be chopped");
1240                return False;
1241
1242             elsif not Is_Regular_File (F) then
1243                Error_Msg (F & " not found");
1244                return False;
1245             end if;
1246          end;
1247       end loop;
1248
1249       return True;
1250
1251    exception
1252       when Invalid_Switch =>
1253          Error_Msg ("invalid switch " & Full_Switch);
1254          return False;
1255
1256       when Invalid_Parameter =>
1257          if Hostparm.OpenVMS then
1258             Error_Msg ("/FILE_NAME_MAX_LENGTH=nnn qualifier" &
1259                        " requires numeric parameter");
1260          else
1261             Error_Msg ("-k switch requires numeric parameter");
1262          end if;
1263
1264          return False;
1265
1266    end Scan_Arguments;
1267
1268    ----------------
1269    -- Sort_Units --
1270    ----------------
1271
1272    procedure Sort_Units is
1273
1274       procedure Move (From : Natural; To : Natural);
1275       --  Procedure used to sort the unit list
1276       --  Unit.Table (To) := Unit_List (From); used by sort
1277
1278       function Lt (Left, Right : Natural) return Boolean;
1279       --  Compares Left and Right units based on file name (first),
1280       --  Chop_File (second) and Offset (third). This ordering is
1281       --  important to keep the last version in case of duplicate files.
1282
1283       package Unit_Sort is new GNAT.Heap_Sort_G (Move, Lt);
1284       --  Used for sorting on filename to detect duplicates
1285
1286       --------
1287       -- Lt --
1288       --------
1289
1290       function Lt (Left, Right : Natural) return Boolean is
1291          L : Unit_Info renames
1292                Unit.Table (Sorted_Units.Table (SUnit_Num (Left)));
1293
1294          R : Unit_Info renames
1295                Unit.Table (Sorted_Units.Table (SUnit_Num (Right)));
1296
1297       begin
1298          return L.File_Name.all < R.File_Name.all
1299            or else (L.File_Name.all = R.File_Name.all
1300                      and then (L.Chop_File < R.Chop_File
1301                                  or else (L.Chop_File = R.Chop_File
1302                                             and then L.Offset < R.Offset)));
1303       end Lt;
1304
1305       ----------
1306       -- Move --
1307       ----------
1308
1309       procedure Move (From : Natural; To : Natural) is
1310       begin
1311          Sorted_Units.Table (SUnit_Num (To)) :=
1312            Sorted_Units.Table (SUnit_Num (From));
1313       end Move;
1314
1315    --  Start of processing for Sort_Units
1316
1317    begin
1318       Sorted_Units.Set_Last (SUnit_Num (Unit.Last));
1319
1320       for J in 1 .. Unit.Last loop
1321          Sorted_Units.Table (SUnit_Num (J)) := J;
1322       end loop;
1323
1324       --  Sort Unit.Table, using Sorted_Units.Table (0) as scratch
1325
1326       Unit_Sort.Sort (Natural (Unit.Last));
1327
1328       --  Set the Sorted_Index fields in the unit tables
1329
1330       for J in 1 .. SUnit_Num (Unit.Last) loop
1331          Unit.Table (Sorted_Units.Table (J)).Sorted_Index := J;
1332       end loop;
1333    end Sort_Units;
1334
1335    -----------
1336    -- Usage --
1337    -----------
1338
1339    procedure Usage is
1340    begin
1341       Put_Line
1342         ("Usage: gnatchop [-c] [-h] [-k#] " &
1343          "[-r] [-p] [-q] [-v] [-w] [-x] [--GCC=xx] file [file ...] [dir]");
1344
1345       New_Line;
1346       Put_Line
1347         ("  -c       compilation mode, configuration pragmas " &
1348          "follow RM rules");
1349
1350       Put_Line
1351         ("  -gnatxxx passes the -gnatxxx switch to gnat parser");
1352
1353       Put_Line
1354         ("  -h       help: output this usage information");
1355
1356       Put_Line
1357         ("  -k#      krunch file names of generated files to " &
1358          "no more than # characters");
1359
1360       Put_Line
1361         ("  -k       krunch file names of generated files to " &
1362          "no more than 8 characters");
1363
1364       Put_Line
1365         ("  -p       preserve time stamp, output files will " &
1366          "have same stamp as input");
1367
1368       Put_Line
1369         ("  -q       quiet mode, no output of generated file " &
1370          "names");
1371
1372       Put_Line
1373         ("  -r       generate Source_Reference pragmas refer" &
1374          "encing original source file");
1375
1376       Put_Line
1377         ("  -v       verbose mode, output version and generat" &
1378          "ed commands");
1379
1380       Put_Line
1381         ("  -w       overwrite existing filenames");
1382
1383       Put_Line
1384         ("  -x       exit on error");
1385
1386       Put_Line
1387         ("  --GCC=xx specify the path of the gnat parser to be used");
1388
1389       New_Line;
1390       Put_Line
1391         ("  file...  list of source files to be chopped");
1392
1393       Put_Line
1394         ("  dir      directory location for split files (defa" &
1395          "ult = current directory)");
1396    end Usage;
1397
1398    -----------------
1399    -- Warning_Msg --
1400    -----------------
1401
1402    procedure Warning_Msg (Message : String) is
1403    begin
1404       Warning_Count := Warning_Count + 1;
1405       Put_Line (Standard_Error, "warning: " & Message);
1406    end Warning_Msg;
1407
1408    -------------------------
1409    -- Write_Chopped_Files --
1410    -------------------------
1411
1412    function Write_Chopped_Files (Input : File_Num) return Boolean is
1413       Name    : aliased constant String :=
1414                   File.Table (Input).Name.all & ASCII.Nul;
1415       FD      : File_Descriptor;
1416       Buffer  : String_Access;
1417       Success : Boolean;
1418       TS_Time : OS_Time;
1419
1420    begin
1421       FD := Open_Read (Name'Address, Binary);
1422       TS_Time := File_Time_Stamp (FD);
1423
1424       if FD = Invalid_FD then
1425          Error_Msg ("cannot open " & File.Table (Input).Name.all);
1426          return False;
1427       end if;
1428
1429       Read_File (FD, Buffer, Success);
1430
1431       if not Success then
1432          Error_Msg ("cannot read " & File.Table (Input).Name.all);
1433          Close (FD);
1434          return False;
1435       end if;
1436
1437       if not Quiet_Mode then
1438          Put_Line ("splitting " & File.Table (Input).Name.all & " into:");
1439       end if;
1440
1441       --  Only chop those units that come from this file
1442
1443       for Num in 1 .. Unit.Last loop
1444          if Unit.Table (Num).Chop_File = Input then
1445             Write_Unit (Buffer, Num, TS_Time, Success);
1446             exit when not Success;
1447          end if;
1448       end loop;
1449
1450       Close (FD);
1451       return Success;
1452    end Write_Chopped_Files;
1453
1454    -----------------------
1455    -- Write_Config_File --
1456    -----------------------
1457
1458    procedure Write_Config_File (Input : File_Num; U : Unit_Num) is
1459       FD      : File_Descriptor;
1460       Name    : aliased constant String := "gnat.adc" & ASCII.NUL;
1461       Buffer  : String_Access;
1462       Success : Boolean;
1463       Append  : Boolean;
1464       Buffera : String_Access;
1465       Bufferl : Natural;
1466
1467    begin
1468       Write_gnat_adc := True;
1469       FD := Open_Read_Write (Name'Address, Binary);
1470
1471       if FD = Invalid_FD then
1472          FD := Create_File (Name'Address, Binary);
1473          Append := False;
1474
1475          if not Quiet_Mode then
1476             Put_Line ("writing configuration pragmas from " &
1477                File.Table (Input).Name.all & " to gnat.adc");
1478          end if;
1479
1480       else
1481          Append := True;
1482
1483          if not Quiet_Mode then
1484             Put_Line
1485               ("appending configuration pragmas from " &
1486                File.Table (Input).Name.all & " to gnat.adc");
1487          end if;
1488       end if;
1489
1490       Success := FD /= Invalid_FD;
1491
1492       if not Success then
1493          Error_Msg ("cannot create gnat.adc");
1494          return;
1495       end if;
1496
1497       --  In append mode, acquire existing gnat.adc file
1498
1499       if Append then
1500          Read_File (FD, Buffera, Success);
1501
1502          if not Success then
1503             Error_Msg ("cannot read gnat.adc");
1504             return;
1505          end if;
1506
1507          --  Find location of EOF byte if any to exclude from append
1508
1509          Bufferl := 1;
1510          while Bufferl <= Buffera'Last
1511            and then Buffera (Bufferl) /= EOF
1512          loop
1513             Bufferl := Bufferl + 1;
1514          end loop;
1515
1516          Bufferl := Bufferl - 1;
1517          Close (FD);
1518
1519          --  Write existing gnat.adc to new gnat.adc file
1520
1521          FD := Create_File (Name'Address, Binary);
1522          Success := Write (FD, Buffera (1)'Address, Bufferl) = Bufferl;
1523
1524          if not Success then
1525             Error_Msg ("error writing gnat.adc");
1526             return;
1527          end if;
1528       end if;
1529
1530       Buffer := Get_Config_Pragmas  (Input, U);
1531
1532       if Buffer /= null then
1533          Success := Write (FD, Buffer.all'Address, Buffer'Length) =
1534                                  Buffer'Length;
1535
1536          if not Success then
1537             Error_Msg ("disk full writing gnat.adc");
1538             return;
1539          end if;
1540       end if;
1541
1542       Close (FD);
1543    end Write_Config_File;
1544
1545    -----------------------------------
1546    -- Write_Source_Reference_Pragma --
1547    -----------------------------------
1548
1549    procedure Write_Source_Reference_Pragma
1550      (Info    : Unit_Info;
1551       Line    : Line_Num;
1552       File    : Stream_IO.File_Type;
1553       EOL     : EOL_String;
1554       Success : in out Boolean)
1555    is
1556       FTE : File_Entry renames Gnatchop.File.Table (Info.Chop_File);
1557       Nam : String_Access;
1558
1559    begin
1560       if Success and Source_References and not Info.SR_Present then
1561          if FTE.SR_Name /= null then
1562             Nam := FTE.SR_Name;
1563          else
1564             Nam := FTE.Name;
1565          end if;
1566
1567          declare
1568             Reference : String :=
1569                           "pragma Source_Reference (000000, """
1570                             & Nam.all & """);" & EOL.Str;
1571
1572             Pos : Positive := Reference'First;
1573             Lin : Line_Num := Line;
1574
1575          begin
1576             while Reference (Pos + 1) /= ',' loop
1577                Pos := Pos + 1;
1578             end loop;
1579
1580             while Reference (Pos) = '0' loop
1581                Reference (Pos) := Character'Val
1582                  (Character'Pos ('0') + Lin mod 10);
1583                Lin := Lin / 10;
1584                Pos := Pos - 1;
1585             end loop;
1586
1587             --  Assume there are enough zeroes for any program length
1588
1589             pragma Assert (Lin = 0);
1590
1591             begin
1592                String'Write (Stream_IO.Stream (File), Reference);
1593                Success := True;
1594             exception
1595                when others =>
1596                   Success := False;
1597             end;
1598          end;
1599       end if;
1600    end Write_Source_Reference_Pragma;
1601
1602    ----------------
1603    -- Write_Unit --
1604    ----------------
1605
1606    procedure Write_Unit
1607      (Source  : not null access String;
1608       Num     : Unit_Num;
1609       TS_Time : OS_Time;
1610       Success : out Boolean)
1611    is
1612
1613       procedure OS_Filename
1614         (Name     : String;
1615          W_Name   : Wide_String;
1616          OS_Name  : Address;
1617          N_Length : access Natural;
1618          Encoding : Address;
1619          E_Length : access Natural);
1620       pragma Import (C, OS_Filename, "__gnat_os_filename");
1621       --  Returns in OS_Name the proper name for the OS when used with the
1622       --  returned Encoding value. For example on Windows this will return the
1623       --  UTF-8 encoded name into OS_Name and set Encoding to encoding=utf8
1624       --  (form parameter Stream_IO).
1625       --  Name is the filename and W_Name the same filename in Unicode 16 bits
1626       --  (this corresponds to Win32 Unicode ISO/IEC 10646). N_Length and
1627       --  E_Length are the length returned in OS_Name and Encoding
1628       --  respectively.
1629
1630       Info     : Unit_Info renames Unit.Table (Num);
1631       Name     : aliased constant String := Info.File_Name.all & ASCII.NUL;
1632       W_Name   : aliased constant Wide_String := To_Wide_String (Name);
1633       EOL      : constant EOL_String :=
1634                    Get_EOL (Source, Source'First + Info.Offset);
1635
1636       OS_Name  : aliased String (1 .. Name'Length * 2);
1637       O_Length : aliased Natural := OS_Name'Length;
1638       Encoding : aliased String (1 .. 64);
1639       E_Length : aliased Natural := Encoding'Length;
1640
1641       Length   : File_Offset;
1642
1643    begin
1644       --  Skip duplicated files
1645
1646       if Is_Duplicated (Info.Sorted_Index) then
1647          Put_Line ("   " & Info.File_Name.all & " skipped");
1648          Success := Overwrite_Files;
1649          return;
1650       end if;
1651
1652       --  Get OS filename
1653
1654       OS_Filename
1655         (Name, W_Name,
1656          OS_Name'Address, O_Length'Access,
1657          Encoding'Address, E_Length'Access);
1658
1659       declare
1660          E_Name      : constant String := OS_Name (1 .. O_Length);
1661          C_Name      : aliased constant String := E_Name & ASCII.Nul;
1662          OS_Encoding : constant String := Encoding (1 .. E_Length);
1663          File        : Stream_IO.File_Type;
1664       begin
1665          begin
1666             if not Overwrite_Files and then Exists (E_Name) then
1667                raise Stream_IO.Name_Error;
1668             else
1669                Stream_IO.Create
1670                  (File, Stream_IO.Out_File, E_Name, OS_Encoding);
1671                Success := True;
1672             end if;
1673          exception
1674             when Stream_IO.Name_Error | Stream_IO.Use_Error =>
1675                Error_Msg ("cannot create " & Info.File_Name.all);
1676                return;
1677          end;
1678
1679          --  A length of 0 indicates that the rest of the file belongs to
1680          --  this unit. The actual length must be calculated now. Take into
1681          --  account that the last character (EOF) must not be written.
1682
1683          if Info.Length = 0 then
1684             Length := Source'Last - (Source'First + Info.Offset);
1685          else
1686             Length := Info.Length;
1687          end if;
1688
1689          --  Prepend configuration pragmas if necessary
1690
1691          if Success and then Info.Bufferg /= null then
1692             Write_Source_Reference_Pragma (Info, 1, File, EOL, Success);
1693
1694             String'Write (Stream_IO.Stream (File), Info.Bufferg.all);
1695          end if;
1696
1697          Write_Source_Reference_Pragma
1698            (Info, Info.Start_Line, File, EOL, Success);
1699
1700          if Success then
1701             begin
1702                String'Write
1703                  (Stream_IO.Stream (File),
1704                   Source (Source'First + Info.Offset ..
1705                       Source'First + Info.Offset + Length - 1));
1706             exception
1707                when Stream_IO.Use_Error | Stream_IO.Device_Error =>
1708                   Error_Msg ("disk full writing " & Info.File_Name.all);
1709                   return;
1710             end;
1711          end if;
1712
1713          if not Quiet_Mode then
1714             Put_Line ("   " & Info.File_Name.all);
1715          end if;
1716
1717          Stream_IO.Close (File);
1718
1719          if Preserve_Mode then
1720             File_Time_Stamp (C_Name'Address, TS_Time);
1721          end if;
1722       end;
1723    end Write_Unit;
1724
1725 --  Start of processing for gnatchop
1726
1727 begin
1728    --  Add the directory where gnatchop is invoked in front of the
1729    --  path, if gnatchop is invoked with directory information.
1730    --  Only do this if the platform is not VMS, where the notion of path
1731    --  does not really exist.
1732
1733    if not Hostparm.OpenVMS then
1734       declare
1735          Command : constant String := Command_Name;
1736
1737       begin
1738          for Index in reverse Command'Range loop
1739             if Command (Index) = Directory_Separator then
1740                declare
1741                   Absolute_Dir : constant String :=
1742                                    Normalize_Pathname
1743                                      (Command (Command'First .. Index));
1744
1745                   PATH         : constant String :=
1746                                    Absolute_Dir &
1747                   Path_Separator &
1748                   Getenv ("PATH").all;
1749
1750                begin
1751                   Setenv ("PATH", PATH);
1752                end;
1753
1754                exit;
1755             end if;
1756          end loop;
1757       end;
1758    end if;
1759
1760    --  Process command line options and initialize global variables
1761
1762    --  First, scan to detect --version and/or --help
1763
1764    Check_Version_And_Help ("GNATCHOP", "1998", Usage'Unrestricted_Access);
1765
1766    if not Scan_Arguments then
1767       Set_Exit_Status (Failure);
1768       return;
1769    end if;
1770
1771    --  Check presence of required executables
1772
1773    Gnat_Cmd := Locate_Executable (Gcc.all, not Gcc_Set);
1774
1775    if Gnat_Cmd = null then
1776       goto No_Files_Written;
1777    end if;
1778
1779    --  First parse all files and read offset information
1780
1781    for Num in 1 .. File.Last loop
1782       if not Parse_File (Num) then
1783          goto No_Files_Written;
1784       end if;
1785    end loop;
1786
1787    --  Check if any units have been found (assumes non-empty Unit.Table)
1788
1789    if Unit.Last = 0 then
1790       if not Write_gnat_adc then
1791          Error_Msg ("no compilation units found", Warning => True);
1792       end if;
1793
1794       goto No_Files_Written;
1795    end if;
1796
1797    Sort_Units;
1798
1799    --  Check if any duplicate files would be created. If so, emit
1800    --  a warning if Overwrite_Files is true, otherwise generate an error.
1801
1802    if Report_Duplicate_Units and then not Overwrite_Files then
1803       goto No_Files_Written;
1804    end if;
1805
1806    --  Check if any files exist, if so do not write anything
1807    --  Because all files have been parsed and checked already,
1808    --  there won't be any duplicates
1809
1810    if not Overwrite_Files and then Files_Exist then
1811       goto No_Files_Written;
1812    end if;
1813
1814    --  After this point, all source files are read in succession
1815    --  and chopped into their destination files.
1816
1817    --  As the Source_File_Name pragmas are handled as logical file 0,
1818    --  write it first.
1819
1820    for F in 1 .. File.Last loop
1821       if not Write_Chopped_Files (F) then
1822          Set_Exit_Status (Failure);
1823          return;
1824       end if;
1825    end loop;
1826
1827    if Warning_Count > 0 then
1828       declare
1829          Warnings_Msg : constant String := Warning_Count'Img & " warning(s)";
1830       begin
1831          Error_Msg (Warnings_Msg (2 .. Warnings_Msg'Last), Warning => True);
1832       end;
1833    end if;
1834
1835    return;
1836
1837 <<No_Files_Written>>
1838
1839    --  Special error exit for all situations where no files have
1840    --  been written.
1841
1842    if not Write_gnat_adc then
1843       Error_Msg ("no source files written", Warning => True);
1844    end if;
1845
1846    return;
1847
1848 exception
1849    when Types.Terminate_Program =>
1850       null;
1851
1852 end Gnatchop;