------------------------------------------------------------------------------ -- -- -- GNAT COMPILER COMPONENTS -- -- -- -- G N A T C H O P -- -- -- -- B o d y -- -- -- -- Copyright (C) 1998-2020, Free Software Foundation, Inc. -- -- -- -- GNAT is free software; you can redistribute it and/or modify it under -- -- terms of the GNU General Public License as published by the Free Soft- -- -- ware Foundation; either version 3, or (at your option) any later ver- -- -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -- -- for more details. You should have received a copy of the GNU General -- -- Public License distributed with GNAT; see file COPYING3. If not, go to -- -- http://www.gnu.org/licenses for a complete copy of the license. -- -- -- -- GNAT was originally developed by the GNAT team at New York University. -- -- Extensive contributions were provided by Ada Core Technologies Inc. -- -- -- ------------------------------------------------------------------------------ with Ada.Characters.Conversions; use Ada.Characters.Conversions; with Ada.Command_Line; use Ada.Command_Line; with Ada.Directories; use Ada.Directories; with Ada.Streams.Stream_IO; use Ada.Streams; with Ada.Text_IO; use Ada.Text_IO; with System.CRTL; use System; use System.CRTL; with GNAT.Byte_Order_Mark; use GNAT.Byte_Order_Mark; with GNAT.Command_Line; use GNAT.Command_Line; with GNAT.OS_Lib; use GNAT.OS_Lib; with GNAT.Heap_Sort_G; with GNAT.Table; with Switch; use Switch; with Types; procedure Gnatchop is Config_File_Name : constant String_Access := new String'("gnat.adc"); -- The name of the file holding the GNAT configuration pragmas Gcc : String_Access := new String'("gcc"); -- May be modified by switch --GCC= Gcc_Set : Boolean := False; -- True if a switch --GCC= is used Gnat_Cmd : String_Access; -- Command to execute the GNAT compiler Gnat_Args : Argument_List_Access := new Argument_List' (new String'("-c"), new String'("-x"), new String'("ada"), new String'("-gnats"), new String'("-gnatu")); -- Arguments used in Gnat_Cmd call EOF : constant Character := Character'Val (26); -- Special character to signal end of file. Not required in input files, -- but properly treated if present. Not generated in output files except -- as a result of copying input file. BOM_Length : Natural := 0; -- Reset to non-zero value if BOM detected at start of file -------------------- -- File arguments -- -------------------- subtype File_Num is Natural; subtype File_Offset is Natural; type File_Entry is record Name : String_Access; -- Name of chop file or directory SR_Name : String_Access; -- Null unless the chop file starts with a source reference pragma -- in which case this field points to the file name from this pragma. end record; package File is new GNAT.Table (Table_Component_Type => File_Entry, Table_Index_Type => File_Num, Table_Low_Bound => 1, Table_Initial => 100, Table_Increment => 100); Directory : String_Access; -- Record name of directory, or a null string if no directory given Compilation_Mode : Boolean := False; Overwrite_Files : Boolean := False; Preserve_Mode : Boolean := False; Quiet_Mode : Boolean := False; Source_References : Boolean := False; Verbose_Mode : Boolean := False; Exit_On_Error : Boolean := False; -- Global options Write_gnat_adc : Boolean := False; -- Gets set true if we append to gnat.adc or create a new gnat.adc. -- Used to inhibit complaint about no units generated. --------------- -- Unit list -- --------------- type Line_Num is new Natural; -- Line number (for source reference pragmas) type Unit_Count_Type is new Integer; subtype Unit_Num is Unit_Count_Type range 1 .. Unit_Count_Type'Last; -- Used to refer to unit number in unit table type SUnit_Num is new Integer; -- Used to refer to entry in sorted units table. Note that entry -- zero is only for use by Heapsort, and is not otherwise referenced. type Unit_Kind is (Unit_Spec, Unit_Body, Config_Pragmas); -- Structure to contain all necessary information for one unit. -- Entries are also temporarily used to record config pragma sequences. type Unit_Info is record File_Name : String_Access; -- File name from GNAT output line Chop_File : File_Num; -- File number in chop file sequence Start_Line : Line_Num; -- Line number from GNAT output line Offset : File_Offset; -- Offset name from GNAT output line SR_Present : Boolean; -- Set True if SR parameter present Length : File_Offset; -- A length of 0 means that the Unit is the last one in the file Kind : Unit_Kind; -- Indicates kind of unit Sorted_Index : SUnit_Num; -- Index of unit in sorted unit list Bufferg : String_Access; -- Pointer to buffer containing configuration pragmas to be prepended. -- Null if no pragmas to be prepended. end record; -- The following table stores the unit offset information package Unit is new GNAT.Table (Table_Component_Type => Unit_Info, Table_Index_Type => Unit_Count_Type, Table_Low_Bound => 1, Table_Initial => 500, Table_Increment => 100); -- The following table is used as a sorted index to the Unit.Table. -- The entries in Unit.Table are not moved, instead we just shuffle -- the entries in Sorted_Units. Note that the zeroeth entry in this -- table is used by GNAT.Heap_Sort_G. package Sorted_Units is new GNAT.Table (Table_Component_Type => Unit_Num, Table_Index_Type => SUnit_Num, Table_Low_Bound => 0, Table_Initial => 500, Table_Increment => 100); function Is_Duplicated (U : SUnit_Num) return Boolean; -- Returns true if U is duplicated by a later unit. -- Note that this function returns false for the last entry. procedure Sort_Units; -- Sort units and set up sorted unit table ---------------------- -- File_Descriptors -- ---------------------- function dup (handle : File_Descriptor) return File_Descriptor; function dup2 (from, to : File_Descriptor) return File_Descriptor; --------------------- -- Local variables -- --------------------- Warning_Count : Natural := 0; -- Count of warnings issued so far ----------------------- -- Local subprograms -- ----------------------- procedure Error_Msg (Message : String; Warning : Boolean := False); -- Produce an error message on standard error output function Files_Exist return Boolean; -- Check Unit.Table for possible file names that already exist -- in the file system. Returns true if files exist, False otherwise function Get_Maximum_File_Name_Length return Integer; pragma Import (C, Get_Maximum_File_Name_Length, "__gnat_get_maximum_file_name_length"); -- Function to get maximum file name length for system Maximum_File_Name_Length : constant Integer := Get_Maximum_File_Name_Length; Maximum_File_Name_Length_String : constant String := Integer'Image (Maximum_File_Name_Length); function Locate_Executable (Program_Name : String; Look_For_Prefix : Boolean := True) return String_Access; -- Locate executable for given program name. This takes into account -- the target-prefix of the current command, if Look_For_Prefix is True. subtype EOL_Length is Natural range 0 .. 2; -- Possible lengths of end of line sequence type EOL_String (Len : EOL_Length := 0) is record Str : String (1 .. Len); end record; function Get_EOL (Source : not null access String; Start : Positive) return EOL_String; -- Return the line terminator used in the passed string procedure Parse_EOL (Source : not null access String; Ptr : in out Positive); -- On return Source (Ptr) is the first character of the next line -- or EOF. Source.all must be terminated by EOF. function Parse_File (Num : File_Num) return Boolean; -- Calls the GNAT compiler to parse the given source file and parses the -- output using Parse_Offset_Info. Returns True if parse operation -- completes, False if some system error (e.g. failure to read the -- offset information) occurs. procedure Parse_Offset_Info (Chop_File : File_Num; Source : not null access String); -- Parses the output of the compiler indicating the offsets and names of -- the compilation units in Chop_File. procedure Parse_Token (Source : not null access String; Ptr : in out Positive; Token_Ptr : out Positive); -- Skips any separators and stores the start of the token in Token_Ptr. -- Then stores the position of the next separator in Ptr. On return -- Source (Token_Ptr .. Ptr - 1) is the token. procedure Read_File (FD : File_Descriptor; Contents : out String_Access; Success : out Boolean); -- Reads file associated with FS into the newly allocated string Contents. -- Success is true iff the number of bytes read is equal to the file size. function Report_Duplicate_Units return Boolean; -- Output messages about duplicate units in the input files in Unit.Table -- Returns True if any duplicates found, False if no duplicates found. function Scan_Arguments return Boolean; -- Scan command line options and set global variables accordingly. -- Also scan out file and directory arguments. Returns True if scan -- was successful, and False if the scan fails for any reason. procedure Usage; -- Output message on standard output describing syntax of gnatchop command procedure Warning_Msg (Message : String); -- Output a warning message on standard error and update warning count function Write_Chopped_Files (Input : File_Num) return Boolean; -- Write all units that result from chopping the Input file procedure Write_Config_File (Input : File_Num; U : Unit_Num); -- Call to write configuration pragmas (append them to gnat.adc). Input is -- the file number for the chop file and U identifies the unit entry for -- the configuration pragmas. function Get_Config_Pragmas (Input : File_Num; U : Unit_Num) return String_Access; -- Call to read configuration pragmas from given unit entry, and return a -- buffer containing the pragmas to be appended to following units. Input -- is the file number for the chop file and U identifies the unit entry for -- the configuration pragmas. procedure Write_Source_Reference_Pragma (Info : Unit_Info; Line : Line_Num; File : Stream_IO.File_Type; EOL : EOL_String; Success : in out Boolean); -- If Success is True on entry, writes a source reference pragma using -- the chop file from Info, and the given line number. On return Success -- indicates whether the write succeeded. If Success is False on entry, -- or if the global flag Source_References is False, then the call to -- Write_Source_Reference_Pragma has no effect. EOL indicates the end -- of line sequence to be written at the end of the pragma. procedure Write_Unit (Source : not null access String; Num : Unit_Num; TS_Time : OS_Time; Write_BOM : Boolean; Success : out Boolean); -- Write one compilation unit of the source to file. Source is the pointer -- to the input string, Num is the unit number, TS_Time is the timestamp, -- Write_BOM is set True to write a UTF-8 BOM at the start of the file. -- Success is set True unless the write attempt fails. --------- -- dup -- --------- function dup (handle : File_Descriptor) return File_Descriptor is begin return File_Descriptor (System.CRTL.dup (int (handle))); end dup; ---------- -- dup2 -- ---------- function dup2 (from, to : File_Descriptor) return File_Descriptor is begin return File_Descriptor (System.CRTL.dup2 (int (from), int (to))); end dup2; --------------- -- Error_Msg -- --------------- procedure Error_Msg (Message : String; Warning : Boolean := False) is begin Put_Line (Standard_Error, Message); if not Warning then Set_Exit_Status (Failure); if Exit_On_Error then raise Types.Terminate_Program; end if; end if; end Error_Msg; ----------------- -- Files_Exist -- ----------------- function Files_Exist return Boolean is Exists : Boolean := False; begin for SNum in 1 .. SUnit_Num (Unit.Last) loop -- Only check and report for the last instance of duplicated files if not Is_Duplicated (SNum) then declare Info : constant Unit_Info := Unit.Table (Sorted_Units.Table (SNum)); begin if Is_Writable_File (Info.File_Name.all) then Error_Msg (Info.File_Name.all & " already exists, use -w to overwrite"); Exists := True; end if; end; end if; end loop; return Exists; end Files_Exist; ------------------------ -- Get_Config_Pragmas -- ------------------------ function Get_Config_Pragmas (Input : File_Num; U : Unit_Num) return String_Access is Info : Unit_Info renames Unit.Table (U); FD : File_Descriptor; Name : aliased constant String := File.Table (Input).Name.all & ASCII.NUL; Length : File_Offset; Buffer : String_Access; Result : String_Access; Success : Boolean; pragma Warnings (Off, Success); begin FD := Open_Read (Name'Address, Binary); if FD = Invalid_FD then Error_Msg ("cannot open " & File.Table (Input).Name.all); return null; end if; Read_File (FD, Buffer, Success); -- A length of 0 indicates that the rest of the file belongs to -- this unit. The actual length must be calculated now. Take into -- account that the last character (EOF) must not be written. if Info.Length = 0 then Length := Buffer'Last - (Buffer'First + Info.Offset); else Length := Info.Length; end if; Result := new String'(Buffer (1 .. Length)); Close (FD); return Result; end Get_Config_Pragmas; ------------- -- Get_EOL -- ------------- function Get_EOL (Source : not null access String; Start : Positive) return EOL_String is Ptr : Positive := Start; First : Positive; Last : Natural; begin -- Skip to end of line while Source (Ptr) /= ASCII.CR and then Source (Ptr) /= ASCII.LF and then Source (Ptr) /= EOF loop Ptr := Ptr + 1; end loop; Last := Ptr; if Source (Ptr) /= EOF then -- Found CR or LF First := Ptr; else First := Ptr + 1; end if; -- Recognize CR/LF if Source (Ptr) = ASCII.CR and then Source (Ptr + 1) = ASCII.LF then Last := First + 1; end if; return (Len => Last + 1 - First, Str => Source (First .. Last)); end Get_EOL; ------------------- -- Is_Duplicated -- ------------------- function Is_Duplicated (U : SUnit_Num) return Boolean is begin return U < SUnit_Num (Unit.Last) and then Unit.Table (Sorted_Units.Table (U)).File_Name.all = Unit.Table (Sorted_Units.Table (U + 1)).File_Name.all; end Is_Duplicated; ----------------------- -- Locate_Executable -- ----------------------- function Locate_Executable (Program_Name : String; Look_For_Prefix : Boolean := True) return String_Access is Gnatchop_Str : constant String := "gnatchop"; Current_Command : constant String := Normalize_Pathname (Command_Name); End_Of_Prefix : Natural; Start_Of_Prefix : Positive; Start_Of_Suffix : Positive; Result : String_Access; begin Start_Of_Prefix := Current_Command'First; Start_Of_Suffix := Current_Command'Last + 1; End_Of_Prefix := Start_Of_Prefix - 1; if Look_For_Prefix then -- Find Start_Of_Prefix for J in reverse Current_Command'Range loop if Current_Command (J) = '/' or else Current_Command (J) = Directory_Separator or else Current_Command (J) = ':' then Start_Of_Prefix := J + 1; exit; end if; end loop; -- Find End_Of_Prefix for J in Start_Of_Prefix .. Current_Command'Last - Gnatchop_Str'Length + 1 loop if Current_Command (J .. J + Gnatchop_Str'Length - 1) = Gnatchop_Str then End_Of_Prefix := J - 1; exit; end if; end loop; end if; if End_Of_Prefix > Current_Command'First then Start_Of_Suffix := End_Of_Prefix + Gnatchop_Str'Length + 1; end if; declare Command : constant String := Current_Command (Start_Of_Prefix .. End_Of_Prefix) & Program_Name & Current_Command (Start_Of_Suffix .. Current_Command'Last); begin Result := Locate_Exec_On_Path (Command); if Result = null then Error_Msg (Command & ": installation problem, executable not found"); end if; end; return Result; end Locate_Executable; --------------- -- Parse_EOL -- --------------- procedure Parse_EOL (Source : not null access String; Ptr : in out Positive) is begin -- Skip to end of line while Source (Ptr) /= ASCII.CR and then Source (Ptr) /= ASCII.LF and then Source (Ptr) /= EOF loop Ptr := Ptr + 1; end loop; if Source (Ptr) /= EOF then Ptr := Ptr + 1; -- skip CR or LF end if; -- Skip past CR/LF or LF/CR combination if (Source (Ptr) = ASCII.CR or else Source (Ptr) = ASCII.LF) and then Source (Ptr) /= Source (Ptr - 1) then Ptr := Ptr + 1; end if; end Parse_EOL; ---------------- -- Parse_File -- ---------------- function Parse_File (Num : File_Num) return Boolean is Chop_Name : constant String_Access := File.Table (Num).Name; Save_Stdout : constant File_Descriptor := dup (Standout); Offset_Name : Temp_File_Name; Offset_FD : File_Descriptor := Invalid_FD; Buffer : String_Access; Success : Boolean; Failure : exception; begin -- Display copy of GNAT command if verbose mode if Verbose_Mode then Put (Gnat_Cmd.all); for J in 1 .. Gnat_Args'Length loop Put (' '); Put (Gnat_Args (J).all); end loop; Put (' '); Put_Line (Chop_Name.all); end if; -- Create temporary file Create_Temp_File (Offset_FD, Offset_Name); if Offset_FD = Invalid_FD then Error_Msg ("gnatchop: cannot create temporary file"); Close (Save_Stdout); return False; end if; -- Redirect Stdout to this temporary file in the Unix way if dup2 (Offset_FD, Standout) = Invalid_FD then Error_Msg ("gnatchop: cannot redirect stdout to temporary file"); Close (Save_Stdout); Close (Offset_FD); return False; end if; -- Call Gnat on the source filename argument with special options -- to generate offset information. If this special compilation completes -- successfully then we can do the actual gnatchop operation. Spawn (Gnat_Cmd.all, Gnat_Args.all & Chop_Name, Success); if not Success then Error_Msg (Chop_Name.all & ": parse errors detected"); Error_Msg (Chop_Name.all & ": chop may not be successful"); end if; -- Restore stdout if dup2 (Save_Stdout, Standout) = Invalid_FD then Error_Msg ("gnatchop: cannot restore stdout"); end if; -- Reopen the file to start reading from the beginning Close (Offset_FD); Close (Save_Stdout); Offset_FD := Open_Read (Offset_Name'Address, Binary); if Offset_FD = Invalid_FD then Error_Msg ("gnatchop: cannot access offset info"); raise Failure; end if; Read_File (Offset_FD, Buffer, Success); if not Success then Error_Msg ("gnatchop: error reading offset info"); Close (Offset_FD); raise Failure; else Parse_Offset_Info (Num, Buffer); end if; -- Close and delete temporary file Close (Offset_FD); Delete_File (Offset_Name'Address, Success); return Success; exception when Failure | Types.Terminate_Program => if Offset_FD /= Invalid_FD then Close (Offset_FD); end if; Delete_File (Offset_Name'Address, Success); return False; end Parse_File; ----------------------- -- Parse_Offset_Info -- ----------------------- procedure Parse_Offset_Info (Chop_File : File_Num; Source : not null access String) is First_Unit : constant Unit_Num := Unit.Last + 1; Bufferg : String_Access := null; Parse_Ptr : File_Offset := Source'First; Token_Ptr : File_Offset; Info : Unit_Info; function Match (Literal : String) return Boolean; -- Checks if given string appears at the current Token_Ptr location -- and if so, bumps Parse_Ptr past the token and returns True. If -- the string is not present, sets Parse_Ptr to Token_Ptr and -- returns False. ----------- -- Match -- ----------- function Match (Literal : String) return Boolean is begin Parse_Token (Source, Parse_Ptr, Token_Ptr); if Source'Last + 1 - Token_Ptr < Literal'Length or else Source (Token_Ptr .. Token_Ptr + Literal'Length - 1) /= Literal then Parse_Ptr := Token_Ptr; return False; end if; Parse_Ptr := Token_Ptr + Literal'Length; return True; end Match; -- Start of processing for Parse_Offset_Info begin loop -- Set default values, should get changed for all -- units/pragmas except for the last Info.Chop_File := Chop_File; Info.Length := 0; -- Parse the current line of offset information into Info -- and exit the loop if there are any errors or on EOF. -- First case, parse a line in the following format: -- Unit x (spec) line 7, file offset 142, [SR, ]file name x.ads -- Note that the unit name can be an operator name in quotes. -- This is of course illegal, but both GNAT and gnatchop handle -- the case so that this error does not interfere with chopping. -- The SR ir present indicates that a source reference pragma -- was processed as part of this unit (and that therefore no -- Source_Reference pragma should be generated. if Match ("Unit") then Parse_Token (Source, Parse_Ptr, Token_Ptr); if Match ("(body)") then Info.Kind := Unit_Body; elsif Match ("(spec)") then Info.Kind := Unit_Spec; else exit; end if; exit when not Match ("line"); Parse_Token (Source, Parse_Ptr, Token_Ptr); Info.Start_Line := Line_Num'Value (Source (Token_Ptr .. Parse_Ptr - 1)); exit when not Match ("file offset"); Parse_Token (Source, Parse_Ptr, Token_Ptr); Info.Offset := File_Offset'Value (Source (Token_Ptr .. Parse_Ptr - 1)); Info.SR_Present := Match ("SR, "); exit when not Match ("file name"); Parse_Token (Source, Parse_Ptr, Token_Ptr); Info.File_Name := new String' (Directory.all & Source (Token_Ptr .. Parse_Ptr - 1)); Parse_EOL (Source, Parse_Ptr); -- Second case, parse a line of the following form -- Configuration pragmas at line 10, file offset 223 elsif Match ("Configuration pragmas at") then Info.Kind := Config_Pragmas; Info.File_Name := Config_File_Name; exit when not Match ("line"); Parse_Token (Source, Parse_Ptr, Token_Ptr); Info.Start_Line := Line_Num'Value (Source (Token_Ptr .. Parse_Ptr - 1)); exit when not Match ("file offset"); Parse_Token (Source, Parse_Ptr, Token_Ptr); Info.Offset := File_Offset'Value (Source (Token_Ptr .. Parse_Ptr - 1)); Parse_EOL (Source, Parse_Ptr); -- Third case, parse a line of the following form -- Source_Reference pragma for file "filename" -- This appears at the start of the file only, and indicates -- the name to be used on any generated Source_Reference pragmas. elsif Match ("Source_Reference pragma for file ") then Parse_Token (Source, Parse_Ptr, Token_Ptr); File.Table (Chop_File).SR_Name := new String'(Source (Token_Ptr + 1 .. Parse_Ptr - 2)); Parse_EOL (Source, Parse_Ptr); goto Continue; -- Unrecognized keyword or end of file else exit; end if; -- Store the data in the Info record in the Unit.Table Unit.Increment_Last; Unit.Table (Unit.Last) := Info; -- If this is not the first unit from the file, calculate -- the length of the previous unit as difference of the offsets if Unit.Last > First_Unit then Unit.Table (Unit.Last - 1).Length := Info.Offset - Unit.Table (Unit.Last - 1).Offset; end if; -- If not in compilation mode combine current unit with any -- preceding configuration pragmas. if not Compilation_Mode and then Unit.Last > First_Unit and then Unit.Table (Unit.Last - 1).Kind = Config_Pragmas then Info.Start_Line := Unit.Table (Unit.Last - 1).Start_Line; Info.Offset := Unit.Table (Unit.Last - 1).Offset; -- Delete the configuration pragma entry Unit.Table (Unit.Last - 1) := Info; Unit.Decrement_Last; end if; -- If in compilation mode, and previous entry is the initial -- entry for the file and is for configuration pragmas, then -- they are to be appended to every unit in the file. if Compilation_Mode and then Unit.Last = First_Unit + 1 and then Unit.Table (First_Unit).Kind = Config_Pragmas then Bufferg := Get_Config_Pragmas (Unit.Table (Unit.Last - 1).Chop_File, First_Unit); Unit.Table (Unit.Last - 1) := Info; Unit.Decrement_Last; end if; Unit.Table (Unit.Last).Bufferg := Bufferg; -- If in compilation mode, and this is not the first item, -- combine configuration pragmas with previous unit, which -- will cause an error message to be generated when the unit -- is compiled. if Compilation_Mode and then Unit.Last > First_Unit and then Unit.Table (Unit.Last).Kind = Config_Pragmas then Unit.Decrement_Last; end if; <> null; end loop; -- Find out if the loop was exited prematurely because of -- an error or if the EOF marker was found. if Source (Parse_Ptr) /= EOF then Error_Msg (File.Table (Chop_File).Name.all & ": error parsing offset info"); return; end if; -- Handle case of a chop file consisting only of config pragmas if Unit.Last = First_Unit and then Unit.Table (Unit.Last).Kind = Config_Pragmas then -- In compilation mode, we append such a file to gnat.adc if Compilation_Mode then Write_Config_File (Unit.Table (Unit.Last).Chop_File, First_Unit); Unit.Decrement_Last; -- In default (non-compilation) mode, this is invalid else Error_Msg (File.Table (Chop_File).Name.all & ": no units found (only pragmas)"); Unit.Decrement_Last; end if; end if; -- Handle case of a chop file ending with config pragmas. This can -- happen only in default non-compilation mode, since in compilation -- mode such configuration pragmas are part of the preceding unit. -- We simply concatenate such pragmas to the previous file which -- will cause a compilation error, which is appropriate. if Unit.Last > First_Unit and then Unit.Table (Unit.Last).Kind = Config_Pragmas then Unit.Decrement_Last; end if; end Parse_Offset_Info; ----------------- -- Parse_Token -- ----------------- procedure Parse_Token (Source : not null access String; Ptr : in out Positive; Token_Ptr : out Positive) is In_Quotes : Boolean := False; begin -- Skip separators while Source (Ptr) = ' ' or else Source (Ptr) = ',' loop Ptr := Ptr + 1; end loop; Token_Ptr := Ptr; -- Find end-of-token while (In_Quotes or else not (Source (Ptr) = ' ' or else Source (Ptr) = ',')) and then Source (Ptr) >= ' ' loop if Source (Ptr) = '"' then In_Quotes := not In_Quotes; end if; Ptr := Ptr + 1; end loop; end Parse_Token; --------------- -- Read_File -- --------------- procedure Read_File (FD : File_Descriptor; Contents : out String_Access; Success : out Boolean) is Length : constant File_Offset := File_Offset (File_Length (FD)); -- Include room for EOF char Buffer : String_Access := new String (1 .. Length + 1); This_Read : Integer; Read_Ptr : File_Offset := 1; begin loop This_Read := Read (FD, A => Buffer (Read_Ptr)'Address, N => Length + 1 - Read_Ptr); Read_Ptr := Read_Ptr + Integer'Max (This_Read, 0); exit when This_Read <= 0; end loop; Buffer (Read_Ptr) := EOF; -- Comment needed for the following ??? -- Under what circumstances can the test fail ??? -- What is copy doing in that case??? if Read_Ptr = Length then Contents := Buffer; else Contents := new String (1 .. Read_Ptr); Contents.all := Buffer (1 .. Read_Ptr); Free (Buffer); end if; Success := Read_Ptr = Length + 1; end Read_File; ---------------------------- -- Report_Duplicate_Units -- ---------------------------- function Report_Duplicate_Units return Boolean is US : SUnit_Num; U : Unit_Num; Duplicates : Boolean := False; begin US := 1; while US < SUnit_Num (Unit.Last) loop U := Sorted_Units.Table (US); if Is_Duplicated (US) then Duplicates := True; -- Move to last two versions of duplicated file to make it clearer -- to understand which file is retained in case of overwriting. while US + 1 < SUnit_Num (Unit.Last) loop exit when not Is_Duplicated (US + 1); US := US + 1; end loop; U := Sorted_Units.Table (US); if Overwrite_Files then Warning_Msg (Unit.Table (U).File_Name.all & " is duplicated (all but last will be skipped)"); elsif Unit.Table (U).Chop_File = Unit.Table (Sorted_Units.Table (US + 1)).Chop_File then Error_Msg (Unit.Table (U).File_Name.all & " is duplicated in " & File.Table (Unit.Table (U).Chop_File).Name.all); else Error_Msg (Unit.Table (U).File_Name.all & " in " & File.Table (Unit.Table (U).Chop_File).Name.all & " is duplicated in " & File.Table (Unit.Table (Sorted_Units.Table (US + 1)).Chop_File).Name.all); end if; end if; US := US + 1; end loop; if Duplicates and not Overwrite_Files then Put_Line ("use -w to overwrite files and keep last version"); end if; return Duplicates; end Report_Duplicate_Units; -------------------- -- Scan_Arguments -- -------------------- function Scan_Arguments return Boolean is Kset : Boolean := False; -- Set true if -k switch found begin Initialize_Option_Scan; -- Scan options first loop case Getopt ("c gnat? h k? p q r v w x -GCC=!") is when ASCII.NUL => exit; when '-' => Gcc := new String'(Parameter); Gcc_Set := True; when 'c' => Compilation_Mode := True; when 'g' => Gnat_Args := new Argument_List'(Gnat_Args.all & new String'("-gnat" & Parameter)); when 'h' => Usage; raise Types.Terminate_Program; when 'k' => declare Param : String_Access := new String'(Parameter); begin if Param.all /= "" then for J in Param'Range loop if Param (J) not in '0' .. '9' then Error_Msg ("-k# requires numeric parameter"); return False; end if; end loop; else Param := new String'("8"); end if; Gnat_Args := new Argument_List'(Gnat_Args.all & new String'("-gnatk" & Param.all)); Kset := True; end; when 'p' => Preserve_Mode := True; when 'q' => Quiet_Mode := True; when 'r' => Source_References := True; when 'v' => Verbose_Mode := True; Display_Version ("GNATCHOP", "1998"); when 'w' => Overwrite_Files := True; when 'x' => Exit_On_Error := True; when others => null; end case; end loop; if not Kset and then Maximum_File_Name_Length > 0 then -- If this system has restricted filename lengths, tell gnat1 -- about them, removing the leading blank from the image string. Gnat_Args := new Argument_List'(Gnat_Args.all & new String'("-gnatk" & Maximum_File_Name_Length_String (Maximum_File_Name_Length_String'First + 1 .. Maximum_File_Name_Length_String'Last))); end if; -- Scan file names loop declare S : constant String := Get_Argument (Do_Expansion => True); begin exit when S = ""; File.Increment_Last; File.Table (File.Last).Name := new String'(S); File.Table (File.Last).SR_Name := null; end; end loop; -- Case of more than one file where last file is a directory if File.Last > 1 and then Is_Directory (File.Table (File.Last).Name.all) then Directory := File.Table (File.Last).Name; File.Decrement_Last; -- Make sure Directory is terminated with a directory separator, -- so we can generate the output by just appending a filename. if Directory (Directory'Last) /= Directory_Separator and then Directory (Directory'Last) /= '/' then Directory := new String'(Directory.all & Directory_Separator); end if; -- At least one filename must be given elsif File.Last = 0 then if Argument_Count = 0 then Usage; else Try_Help; end if; return False; -- No directory given, set directory to null, so that we can just -- concatenate the directory name to the file name unconditionally. else Directory := new String'(""); end if; -- Finally check all filename arguments for File_Num in 1 .. File.Last loop declare F : constant String := File.Table (File_Num).Name.all; begin if Is_Directory (F) then Error_Msg (F & " is a directory, cannot be chopped"); return False; elsif not Is_Regular_File (F) then Error_Msg (F & " not found"); return False; end if; end; end loop; return True; exception when Invalid_Switch => Error_Msg ("invalid switch " & Full_Switch); return False; when Invalid_Parameter => Error_Msg ("-k switch requires numeric parameter"); return False; end Scan_Arguments; ---------------- -- Sort_Units -- ---------------- procedure Sort_Units is procedure Move (From : Natural; To : Natural); -- Procedure used to sort the unit list -- Unit.Table (To) := Unit_List (From); used by sort function Lt (Left, Right : Natural) return Boolean; -- Compares Left and Right units based on file name (first), -- Chop_File (second) and Offset (third). This ordering is -- important to keep the last version in case of duplicate files. package Unit_Sort is new GNAT.Heap_Sort_G (Move, Lt); -- Used for sorting on filename to detect duplicates -------- -- Lt -- -------- function Lt (Left, Right : Natural) return Boolean is L : Unit_Info renames Unit.Table (Sorted_Units.Table (SUnit_Num (Left))); R : Unit_Info renames Unit.Table (Sorted_Units.Table (SUnit_Num (Right))); begin return L.File_Name.all < R.File_Name.all or else (L.File_Name.all = R.File_Name.all and then (L.Chop_File < R.Chop_File or else (L.Chop_File = R.Chop_File and then L.Offset < R.Offset))); end Lt; ---------- -- Move -- ---------- procedure Move (From : Natural; To : Natural) is begin Sorted_Units.Table (SUnit_Num (To)) := Sorted_Units.Table (SUnit_Num (From)); end Move; -- Start of processing for Sort_Units begin Sorted_Units.Set_Last (SUnit_Num (Unit.Last)); for J in 1 .. Unit.Last loop Sorted_Units.Table (SUnit_Num (J)) := J; end loop; -- Sort Unit.Table, using Sorted_Units.Table (0) as scratch Unit_Sort.Sort (Natural (Unit.Last)); -- Set the Sorted_Index fields in the unit tables for J in 1 .. SUnit_Num (Unit.Last) loop Unit.Table (Sorted_Units.Table (J)).Sorted_Index := J; end loop; end Sort_Units; ----------- -- Usage -- ----------- procedure Usage is begin Put_Line ("Usage: gnatchop [-c] [-h] [-k#] " & "[-r] [-p] [-q] [-v] [-w] [-x] [--GCC=xx] file [file ...] [dir]"); New_Line; Display_Usage_Version_And_Help; Put_Line (" -c compilation mode, configuration pragmas " & "follow RM rules"); Put_Line (" -gnatxxx passes the -gnatxxx switch to gnat parser"); Put_Line (" -h help: output this usage information"); Put_Line (" -k# krunch file names of generated files to " & "no more than # characters"); Put_Line (" -k krunch file names of generated files to " & "no more than 8 characters"); Put_Line (" -p preserve time stamp, output files will " & "have same stamp as input"); Put_Line (" -q quiet mode, no output of generated file " & "names"); Put_Line (" -r generate Source_Reference pragmas refer" & "encing original source file"); Put_Line (" -v verbose mode, output version and generat" & "ed commands"); Put_Line (" -w overwrite existing filenames"); Put_Line (" -x exit on error"); Put_Line (" --GCC=xx specify the path of the gnat parser to be used"); New_Line; Put_Line (" file... list of source files to be chopped"); Put_Line (" dir directory location for split files (defa" & "ult = current directory)"); end Usage; ----------------- -- Warning_Msg -- ----------------- procedure Warning_Msg (Message : String) is begin Warning_Count := Warning_Count + 1; Put_Line (Standard_Error, "warning: " & Message); end Warning_Msg; ------------------------- -- Write_Chopped_Files -- ------------------------- function Write_Chopped_Files (Input : File_Num) return Boolean is Name : aliased constant String := File.Table (Input).Name.all & ASCII.NUL; FD : File_Descriptor; Buffer : String_Access; Success : Boolean; TS_Time : OS_Time; BOM_Present : Boolean; BOM : BOM_Kind; -- Record presence of UTF8 BOM in input begin FD := Open_Read (Name'Address, Binary); TS_Time := File_Time_Stamp (FD); if FD = Invalid_FD then Error_Msg ("cannot open " & File.Table (Input).Name.all); return False; end if; Read_File (FD, Buffer, Success); if not Success then Error_Msg ("cannot read " & File.Table (Input).Name.all); Close (FD); return False; end if; if not Quiet_Mode then Put_Line ("splitting " & File.Table (Input).Name.all & " into:"); end if; -- Test for presence of BOM Read_BOM (Buffer.all, BOM_Length, BOM, XML_Support => False); BOM_Present := BOM /= Unknown; -- Only chop those units that come from this file for Unit_Number in 1 .. Unit.Last loop if Unit.Table (Unit_Number).Chop_File = Input then Write_Unit (Source => Buffer, Num => Unit_Number, TS_Time => TS_Time, Write_BOM => BOM_Present and then Unit_Number /= 1, Success => Success); exit when not Success; end if; end loop; Close (FD); return Success; end Write_Chopped_Files; ----------------------- -- Write_Config_File -- ----------------------- procedure Write_Config_File (Input : File_Num; U : Unit_Num) is FD : File_Descriptor; Name : aliased constant String := "gnat.adc" & ASCII.NUL; Buffer : String_Access; Success : Boolean; Append : Boolean; Buffera : String_Access; Bufferl : Natural; begin Write_gnat_adc := True; FD := Open_Read_Write (Name'Address, Binary); if FD = Invalid_FD then FD := Create_File (Name'Address, Binary); Append := False; if not Quiet_Mode then Put_Line ("writing configuration pragmas from " & File.Table (Input).Name.all & " to gnat.adc"); end if; else Append := True; if not Quiet_Mode then Put_Line ("appending configuration pragmas from " & File.Table (Input).Name.all & " to gnat.adc"); end if; end if; Success := FD /= Invalid_FD; if not Success then Error_Msg ("cannot create gnat.adc"); return; end if; -- In append mode, acquire existing gnat.adc file if Append then Read_File (FD, Buffera, Success); if not Success then Error_Msg ("cannot read gnat.adc"); return; end if; -- Find location of EOF byte if any to exclude from append Bufferl := 1; while Bufferl <= Buffera'Last and then Buffera (Bufferl) /= EOF loop Bufferl := Bufferl + 1; end loop; Bufferl := Bufferl - 1; Close (FD); -- Write existing gnat.adc to new gnat.adc file FD := Create_File (Name'Address, Binary); Success := Write (FD, Buffera (1)'Address, Bufferl) = Bufferl; if not Success then Error_Msg ("error writing gnat.adc"); return; end if; end if; Buffer := Get_Config_Pragmas (Input, U); if Buffer /= null then Success := Write (FD, Buffer.all'Address, Buffer'Length) = Buffer'Length; if not Success then Error_Msg ("disk full writing gnat.adc"); return; end if; end if; Close (FD); end Write_Config_File; ----------------------------------- -- Write_Source_Reference_Pragma -- ----------------------------------- procedure Write_Source_Reference_Pragma (Info : Unit_Info; Line : Line_Num; File : Stream_IO.File_Type; EOL : EOL_String; Success : in out Boolean) is FTE : File_Entry renames Gnatchop.File.Table (Info.Chop_File); Nam : String_Access; begin if Success and then Source_References and then not Info.SR_Present then if FTE.SR_Name /= null then Nam := FTE.SR_Name; else Nam := FTE.Name; end if; declare Reference : String := "pragma Source_Reference (000000, """ & Nam.all & """);" & EOL.Str; Pos : Positive := Reference'First; Lin : Line_Num := Line; begin while Reference (Pos + 1) /= ',' loop Pos := Pos + 1; end loop; while Reference (Pos) = '0' loop Reference (Pos) := Character'Val (Character'Pos ('0') + Lin mod 10); Lin := Lin / 10; Pos := Pos - 1; end loop; -- Assume there are enough zeroes for any program length pragma Assert (Lin = 0); begin String'Write (Stream_IO.Stream (File), Reference); Success := True; exception when others => Success := False; end; end; end if; end Write_Source_Reference_Pragma; ---------------- -- Write_Unit -- ---------------- procedure Write_Unit (Source : not null access String; Num : Unit_Num; TS_Time : OS_Time; Write_BOM : Boolean; Success : out Boolean) is procedure OS_Filename (Name : String; W_Name : Wide_String; OS_Name : Address; N_Length : access Natural; Encoding : Address; E_Length : access Natural); pragma Import (C, OS_Filename, "__gnat_os_filename"); -- Returns in OS_Name the proper name for the OS when used with the -- returned Encoding value. For example on Windows this will return the -- UTF-8 encoded name into OS_Name and set Encoding to encoding=utf8 -- (the form parameter for Stream_IO). -- -- Name is the filename and W_Name the same filename in Unicode 16 bits -- (this corresponds to Win32 Unicode ISO/IEC 10646). N_Length/E_Length -- are the length returned in OS_Name/Encoding respectively. Info : Unit_Info renames Unit.Table (Num); Name : aliased constant String := Info.File_Name.all & ASCII.NUL; W_Name : aliased constant Wide_String := To_Wide_String (Name); EOL : constant EOL_String := Get_EOL (Source, Source'First + Info.Offset); OS_Name : aliased String (1 .. Name'Length * 2); O_Length : aliased Natural := OS_Name'Length; Encoding : aliased String (1 .. 64); E_Length : aliased Natural := Encoding'Length; Length : File_Offset; begin -- Skip duplicated files if Is_Duplicated (Info.Sorted_Index) then Put_Line (" " & Info.File_Name.all & " skipped"); Success := Overwrite_Files; return; end if; -- Get OS filename OS_Filename (Name, W_Name, OS_Name'Address, O_Length'Access, Encoding'Address, E_Length'Access); declare E_Name : constant String := OS_Name (1 .. O_Length); OS_Encoding : constant String := Encoding (1 .. E_Length); File : Stream_IO.File_Type; begin begin if not Overwrite_Files and then Exists (E_Name) then raise Stream_IO.Name_Error; else Stream_IO.Create (File, Stream_IO.Out_File, E_Name, OS_Encoding); Success := True; end if; exception when Stream_IO.Name_Error | Stream_IO.Use_Error => Error_Msg ("cannot create " & Info.File_Name.all); return; end; -- A length of 0 indicates that the rest of the file belongs to -- this unit. The actual length must be calculated now. Take into -- account that the last character (EOF) must not be written. if Info.Length = 0 then Length := Source'Last - (Source'First + Info.Offset); else Length := Info.Length; end if; -- Write BOM if required if Write_BOM then String'Write (Stream_IO.Stream (File), Source.all (Source'First .. Source'First + BOM_Length - 1)); end if; -- Prepend configuration pragmas if necessary if Success and then Info.Bufferg /= null then Write_Source_Reference_Pragma (Info, 1, File, EOL, Success); String'Write (Stream_IO.Stream (File), Info.Bufferg.all); end if; Write_Source_Reference_Pragma (Info, Info.Start_Line, File, EOL, Success); if Success then begin String'Write (Stream_IO.Stream (File), Source (Source'First + Info.Offset .. Source'First + Info.Offset + Length - 1)); exception when Stream_IO.Use_Error | Stream_IO.Device_Error => Error_Msg ("disk full writing " & Info.File_Name.all); return; end; end if; if not Quiet_Mode then Put_Line (" " & Info.File_Name.all); end if; Stream_IO.Close (File); if Preserve_Mode then Set_File_Last_Modify_Time_Stamp (E_Name, TS_Time); end if; end; end Write_Unit; procedure Check_Version_And_Help is new Check_Version_And_Help_G (Usage); -- Start of processing for gnatchop begin -- Add the directory where gnatchop is invoked in front of the path, if -- gnatchop is invoked with directory information. declare Command : constant String := Command_Name; begin for Index in reverse Command'Range loop if Command (Index) = Directory_Separator then declare Absolute_Dir : constant String := Normalize_Pathname (Command (Command'First .. Index)); PATH : constant String := Absolute_Dir & Path_Separator & Getenv ("PATH").all; begin Setenv ("PATH", PATH); end; exit; end if; end loop; end; -- Process command line options and initialize global variables -- First, scan to detect --version and/or --help Check_Version_And_Help ("GNATCHOP", "1998"); if not Scan_Arguments then Set_Exit_Status (Failure); return; end if; -- Check presence of required executables Gnat_Cmd := Locate_Executable (Gcc.all, not Gcc_Set); if Gnat_Cmd = null then goto No_Files_Written; end if; -- First parse all files and read offset information for Num in 1 .. File.Last loop if not Parse_File (Num) then goto No_Files_Written; end if; end loop; -- Check if any units have been found (assumes non-empty Unit.Table) if Unit.Last = 0 then if not Write_gnat_adc then Error_Msg ("no compilation units found", Warning => True); end if; goto No_Files_Written; end if; Sort_Units; -- Check if any duplicate files would be created. If so, emit a warning if -- Overwrite_Files is true, otherwise generate an error. if Report_Duplicate_Units and then not Overwrite_Files then goto No_Files_Written; end if; -- Check if any files exist, if so do not write anything Because all files -- have been parsed and checked already, there won't be any duplicates if not Overwrite_Files and then Files_Exist then goto No_Files_Written; end if; -- After this point, all source files are read in succession and chopped -- into their destination files. -- Source_File_Name pragmas are handled as logical file 0 so write it first for F in 1 .. File.Last loop if not Write_Chopped_Files (F) then Set_Exit_Status (Failure); return; end if; end loop; if Warning_Count > 0 then declare Warnings_Msg : constant String := Warning_Count'Img & " warning(s)"; begin Error_Msg (Warnings_Msg (2 .. Warnings_Msg'Last), Warning => True); end; end if; return; <> -- Special error exit for all situations where no files have -- been written. if not Write_gnat_adc then Error_Msg ("no source files written", Warning => True); end if; return; exception when Types.Terminate_Program => null; end Gnatchop;