OSDN Git Service

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