1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
9 -- Copyright (C) 2002-2023, Free Software Foundation, Inc. --
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. --
21 -- GNAT was originally developed by the GNAT team at New York University. --
22 -- Extensive contributions were provided by Ada Core Technologies Inc. --
24 ------------------------------------------------------------------------------
26 with Atree
; use Atree
;
29 with Namet
; use Namet
;
31 with Osint
; use Osint
;
32 with Output
; use Output
;
37 with Stringt
; use Stringt
;
38 with Switch
; use Switch
;
39 with Types
; use Types
;
42 with Ada
.Command_Line
; use Ada
.Command_Line
;
43 with Ada
.Text_IO
; use Ada
.Text_IO
;
45 with GNAT
.Case_Util
; use GNAT
.Case_Util
;
46 with GNAT
.Command_Line
;
47 with GNAT
.Directory_Operations
; use GNAT
.Directory_Operations
;
49 with System
.OS_Lib
; use System
.OS_Lib
;
53 Copyright_Displayed
: Boolean := False;
54 -- Used to prevent multiple displays of the copyright notice
56 ------------------------
57 -- Argument Line Data --
58 ------------------------
60 Unix_Line_Terminators
: Boolean := False;
61 -- Set to True with option -T
63 type String_Array
is array (Boolean) of String_Access
;
64 Yes_No
: constant String_Array
:=
65 (False => new String'("YES"),
66 True => new String'("NO"));
68 Infile_Name
: Name_Id
:= No_Name
;
69 Outfile_Name
: Name_Id
:= No_Name
;
70 Deffile_Name
: Name_Id
:= No_Name
;
72 Output_Directory
: Name_Id
:= No_Name
;
73 -- Used when the specified output is an existing directory
75 Input_Directory
: Name_Id
:= No_Name
;
76 -- Used when the specified input and output are existing directories
78 Source_Ref_Pragma
: Boolean := False;
79 -- Record command line options (set if -r switch set)
81 Text_Outfile
: aliased Ada
.Text_IO
.File_Type
;
82 Outfile
: constant File_Access
:= Text_Outfile
'Access;
84 File_Name_Buffer_Initial_Size
: constant := 50;
85 File_Name_Buffer
: String_Access
:=
86 new String (1 .. File_Name_Buffer_Initial_Size
);
87 -- A buffer to build output file names from input file names
93 procedure Display_Copyright
;
94 -- Display the copyright notice
96 procedure Post_Scan
is null;
97 -- Needed by instantiation of Scng below
99 package Scanner
is new Scng
103 Errutil
.Error_Msg_SC
,
104 Errutil
.Error_Msg_SP
,
106 -- The scanner for the preprocessor
108 function Is_ASCII_Letter
(C
: Character) return Boolean;
109 -- True if C is in 'a' .. 'z' or in 'A' .. 'Z'
111 procedure Double_File_Name_Buffer
;
112 -- Double the size of the file name buffer
114 procedure Preprocess_Infile_Name
;
115 -- When the specified output is a directory, preprocess the infile name
116 -- for symbol substitution, to get the output file name.
118 procedure Process_Files
;
119 -- Process the single input file or all the files in the directory tree
120 -- rooted at the input directory.
122 procedure Process_Command_Line_Symbol_Definition
(S
: String);
123 -- Process a -D switch on the command line
125 procedure Put_Char_To_Outfile
(C
: Character);
126 -- Output one character to the output file. Used to initialize the
129 procedure New_EOL_To_Outfile
;
130 -- Output a new line to the output file. Used to initialize the
133 procedure Scan_Command_Line
;
134 -- Scan the switches and the file names
139 -----------------------
140 -- Display_Copyright --
141 -----------------------
143 procedure Display_Copyright
is
145 if not Copyright_Displayed
then
146 Display_Version
("GNAT Preprocessor", "1996");
147 Copyright_Displayed
:= True;
149 end Display_Copyright
;
151 -----------------------------
152 -- Double_File_Name_Buffer --
153 -----------------------------
155 procedure Double_File_Name_Buffer
is
156 New_Buffer
: constant String_Access
:=
157 new String (1 .. 2 * File_Name_Buffer
'Length);
159 New_Buffer
(File_Name_Buffer
'Range) := File_Name_Buffer
.all;
160 Free
(File_Name_Buffer
);
161 File_Name_Buffer
:= New_Buffer
;
162 end Double_File_Name_Buffer
;
168 procedure Gnatprep
is
170 -- Do some initializations (order is important here)
178 -- Initialize the preprocessor
181 (Error_Msg
=> Errutil
.Error_Msg
'Access,
182 Scan
=> Scanner
.Scan
'Access,
183 Set_Ignore_Errors
=> Errutil
.Set_Ignore_Errors
'Access,
184 Put_Char
=> Put_Char_To_Outfile
'Access,
185 New_EOL
=> New_EOL_To_Outfile
'Access);
187 -- Set the scanner characteristics for the preprocessor
189 Scanner
.Set_Special_Character
('#');
190 Scanner
.Set_Special_Character
('$');
191 Scanner
.Set_End_Of_Line_As_Token
(True);
193 -- Initialize the mapping table of symbols to values
195 Prep
.Symbol_Table
.Init
(Prep
.Mapping
);
197 -- Parse the switches and arguments
201 if Opt
.Verbose_Mode
then
205 -- Test we had all the arguments needed
207 if Infile_Name
= No_Name
then
209 -- No input file specified, just output the usage and exit
211 if Argument_Count
= 0 then
214 GNAT
.Command_Line
.Try_Help
;
219 elsif Outfile_Name
= No_Name
then
221 -- No output file specified, exit
223 GNAT
.Command_Line
.Try_Help
;
227 -- If a pragma Source_File_Name, we need to keep line numbers. So, if
228 -- the deleted lines are not put as comment, we must output them as
231 if Source_Ref_Pragma
and not Opt
.Comment_Deleted_Lines
then
232 Opt
.Blank_Deleted_Lines
:= True;
235 -- If we have a definition file, parse it
237 if Deffile_Name
/= No_Name
then
239 Deffile
: Source_File_Index
;
243 Deffile
:= Sinput
.C
.Load_File
(Get_Name_String
(Deffile_Name
));
245 -- Set Main_Source_File to the definition file for the benefit of
248 Sinput
.Main_Source_File
:= Deffile
;
250 if Deffile
= No_Source_File
then
251 Fail
("unable to find definition file """
252 & Get_Name_String
(Deffile_Name
)
254 elsif Deffile
= No_Access_To_Source_File
then
255 Fail
("unabled to read definition file """
256 & Get_Name_String
(Deffile_Name
)
260 Scanner
.Initialize_Scanner
(Deffile
);
262 -- Parse the definition file without "replace in comments"
265 Replace
: constant Boolean := Opt
.Replace_In_Comments
;
267 Opt
.Replace_In_Comments
:= False;
269 Opt
.Replace_In_Comments
:= Replace
;
274 -- If there are errors in the definition file, output them and exit
276 if Total_Errors_Detected
> 0 then
277 Errutil
.Finalize
(Source_Type
=> "definition");
278 Fail
("errors in definition file """
279 & Get_Name_String
(Deffile_Name
)
283 -- If -s switch was specified, print a sorted list of symbol names and
286 if Opt
.List_Preprocessing_Symbols
then
287 Prep
.List_Symbols
(Foreword
=> "");
290 Output_Directory
:= No_Name
;
291 Input_Directory
:= No_Name
;
293 -- Check if the specified output is an existing directory
295 if Is_Directory
(Get_Name_String
(Outfile_Name
)) then
296 Output_Directory
:= Outfile_Name
;
298 -- As the output is an existing directory, check if the input too
301 if Is_Directory
(Get_Name_String
(Infile_Name
)) then
302 Input_Directory
:= Infile_Name
;
306 -- And process the single input or the files in the directory tree
307 -- rooted at the input directory.
312 ---------------------
313 -- Is_ASCII_Letter --
314 ---------------------
316 function Is_ASCII_Letter
(C
: Character) return Boolean is
318 return C
in 'A' .. 'Z' or else C
in 'a' .. 'z';
321 ------------------------
322 -- New_EOL_To_Outfile --
323 ------------------------
325 procedure New_EOL_To_Outfile
is
327 New_Line
(Outfile
.all);
328 end New_EOL_To_Outfile
;
330 ----------------------------
331 -- Preprocess_Infile_Name --
332 ----------------------------
334 procedure Preprocess_Infile_Name
is
342 -- Initialize the buffer with the name of the input file
344 Get_Name_String
(Infile_Name
);
347 while File_Name_Buffer
'Length < Len
loop
348 Double_File_Name_Buffer
;
351 File_Name_Buffer
(1 .. Len
) := Name_Buffer
(1 .. Len
);
353 -- Look for possible symbols in the file name
356 while First
< Len
loop
358 -- A symbol starts with a dollar sign followed by a letter
360 if File_Name_Buffer
(First
) = '$' and then
361 Is_ASCII_Letter
(File_Name_Buffer
(First
+ 1))
365 -- Find the last letter of the symbol
367 while Last
< Len
and then
368 Is_ASCII_Letter
(File_Name_Buffer
(Last
+ 1))
373 -- Get the symbol name id
375 Name_Len
:= Last
- First
;
376 Name_Buffer
(1 .. Name_Len
) :=
377 File_Name_Buffer
(First
+ 1 .. Last
);
378 To_Lower
(Name_Buffer
(1 .. Name_Len
));
381 -- And look for this symbol name in the symbol table
383 for Index
in 1 .. Symbol_Table
.Last
(Mapping
) loop
384 Data
:= Mapping
.Table
(Index
);
386 if Data
.Symbol
= Symbol
then
388 -- We found the symbol. If its value is not a string,
389 -- replace the symbol in the file name with the value of
392 if not Data
.Is_A_String
then
393 String_To_Name_Buffer
(Data
.Value
);
396 Sym_Len
: constant Positive := Last
- First
+ 1;
397 Offset
: constant Integer := Name_Len
- Sym_Len
;
398 New_Len
: constant Natural := Len
+ Offset
;
401 while New_Len
> File_Name_Buffer
'Length loop
402 Double_File_Name_Buffer
;
405 File_Name_Buffer
(Last
+ 1 + Offset
.. New_Len
) :=
406 File_Name_Buffer
(Last
+ 1 .. Len
);
408 Last
:= Last
+ Offset
;
409 File_Name_Buffer
(First
.. Last
) :=
410 Name_Buffer
(1 .. Name_Len
);
418 -- Skip over the symbol name or its value: we are not checking
419 -- for another symbol name in the value.
428 -- We now have the output file name in the buffer. Get the output
429 -- path and put it in Outfile_Name.
431 Get_Name_String
(Output_Directory
);
432 Add_Char_To_Name_Buffer
(Directory_Separator
);
433 Add_Str_To_Name_Buffer
(File_Name_Buffer
(1 .. Len
));
434 Outfile_Name
:= Name_Find
;
435 end Preprocess_Infile_Name
;
437 --------------------------------------------
438 -- Process_Command_Line_Symbol_Definition --
439 --------------------------------------------
441 procedure Process_Command_Line_Symbol_Definition
(S
: String) is
446 -- Check the symbol definition and get the symbol and its value.
447 -- Fail if symbol definition is illegal.
449 Check_Command_Line_Symbol_Definition
(S
, Data
);
451 Symbol
:= Index_Of
(Data
.Symbol
);
453 -- If symbol does not already exist, create a new entry in the mapping
456 if Symbol
= No_Symbol
then
457 Symbol_Table
.Increment_Last
(Mapping
);
458 Symbol
:= Symbol_Table
.Last
(Mapping
);
461 Mapping
.Table
(Symbol
) := Data
;
462 end Process_Command_Line_Symbol_Definition
;
468 procedure Process_Files
is
470 procedure Process_One_File
;
471 -- Process input file Infile_Name and put the result in file
474 procedure Recursive_Process
(In_Dir
: String; Out_Dir
: String);
475 -- Process recursively files in In_Dir. Results go to Out_Dir
477 ----------------------
478 -- Process_One_File --
479 ----------------------
481 procedure Process_One_File
is
482 Infile
: Source_File_Index
;
485 pragma Warnings
(Off
, Modified
);
488 -- Create the output file (fails if this does not work)
492 (File
=> Text_Outfile
,
494 Name
=> Get_Name_String
(Outfile_Name
),
495 Form
=> "Text_Translation=" &
496 Yes_No
(Unix_Line_Terminators
).all);
501 ("unable to create output file """
502 & Get_Name_String
(Outfile_Name
)
506 -- Load the input file
508 Infile
:= Sinput
.C
.Load_File
(Get_Name_String
(Infile_Name
));
510 if Infile
= No_Source_File
then
511 Fail
("unable to find input file """
512 & Get_Name_String
(Infile_Name
)
514 elsif Infile
= No_Access_To_Source_File
then
515 Fail
("unable to read input file """
516 & Get_Name_String
(Infile_Name
)
520 -- Set Main_Source_File to the input file for the benefit of
523 Sinput
.Main_Source_File
:= Infile
;
525 Scanner
.Initialize_Scanner
(Infile
);
527 -- Output the pragma Source_Reference if asked to
529 if Source_Ref_Pragma
then
532 "pragma Source_Reference (1, """ &
533 Get_Name_String
(Sinput
.Full_File_Name
(Infile
)) & """);");
536 -- Preprocess the input file
538 Prep
.Preprocess
(Modified
);
540 -- In verbose mode, if there is no error, report it
542 if Opt
.Verbose_Mode
and then Total_Errors_Detected
= 0 then
543 Errutil
.Finalize
(Source_Type
=> "input");
546 -- If we had some errors, delete the output file, and report them
548 if Total_Errors_Detected
> 0 then
549 if Outfile
/= Standard_Output
then
550 Delete
(Text_Outfile
);
553 Errutil
.Finalize
(Source_Type
=> "input");
557 -- Otherwise, close the output file, and we are done
559 elsif Outfile
/= Standard_Output
then
560 Close
(Text_Outfile
);
562 end Process_One_File
;
564 -----------------------
565 -- Recursive_Process --
566 -----------------------
568 procedure Recursive_Process
(In_Dir
: String; Out_Dir
: String) is
570 Name
: String (1 .. 255);
572 In_Dir_Name
: Name_Id
;
573 Out_Dir_Name
: Name_Id
;
575 procedure Set_Directory_Names
;
576 -- Establish or reestablish the current input and output directories
578 -------------------------
579 -- Set_Directory_Names --
580 -------------------------
582 procedure Set_Directory_Names
is
584 Input_Directory
:= In_Dir_Name
;
585 Output_Directory
:= Out_Dir_Name
;
586 end Set_Directory_Names
;
588 -- Start of processing for Recursive_Process
591 -- Open the current input directory
594 Open
(Dir_In
, In_Dir
);
597 when Directory_Error
=>
598 Fail
("could not read directory " & In_Dir
);
601 -- Set the new input and output directory names
603 Name_Len
:= In_Dir
'Length;
604 Name_Buffer
(1 .. Name_Len
) := In_Dir
;
605 In_Dir_Name
:= Name_Find
;
606 Name_Len
:= Out_Dir
'Length;
607 Name_Buffer
(1 .. Name_Len
) := Out_Dir
;
608 Out_Dir_Name
:= Name_Find
;
612 -- Traverse the input directory
614 Read
(Dir_In
, Name
, Last
);
617 if Name
(1 .. Last
) /= "." and then Name
(1 .. Last
) /= ".." then
619 Input
: constant String :=
620 In_Dir
& Directory_Separator
& Name
(1 .. Last
);
621 Output
: constant String :=
622 Out_Dir
& Directory_Separator
& Name
(1 .. Last
);
625 -- If input is an ordinary file, process it
627 if Is_Regular_File
(Input
) then
628 -- First get the output file name
631 Name_Buffer
(1 .. Name_Len
) := Name
(1 .. Last
);
632 Infile_Name
:= Name_Find
;
633 Preprocess_Infile_Name
;
635 -- Set the input file name and process the file
637 Name_Len
:= Input
'Length;
638 Name_Buffer
(1 .. Name_Len
) := Input
;
639 Infile_Name
:= Name_Find
;
642 elsif Is_Directory
(Input
) then
643 -- Input is a directory. If the corresponding output
644 -- directory does not already exist, create it.
646 if not Is_Directory
(Output
) then
648 Make_Dir
(Dir_Name
=> Output
);
651 when Directory_Error
=>
652 Fail
("could not create directory """
658 -- And process this new input directory
660 Recursive_Process
(Input
, Output
);
662 -- Reestablish the input and output directory names
663 -- that have been modified by the recursive call.
670 end Recursive_Process
;
672 -- Start of processing for Process_Files
675 if Output_Directory
= No_Name
then
677 -- If the output is not a directory, fail if the input is
678 -- an existing directory, to avoid possible problems.
680 if Is_Directory
(Get_Name_String
(Infile_Name
)) then
681 Fail
("input file """ & Get_Name_String
(Infile_Name
) &
682 """ is a directory");
685 -- Just process the single input file
689 elsif Input_Directory
= No_Name
then
691 -- Get the output file name from the input file name, and process
692 -- the single input file.
694 Preprocess_Infile_Name
;
698 -- Recursively process files in the directory tree rooted at the
702 (In_Dir
=> Get_Name_String
(Input_Directory
),
703 Out_Dir
=> Get_Name_String
(Output_Directory
));
707 -------------------------
708 -- Put_Char_To_Outfile --
709 -------------------------
711 procedure Put_Char_To_Outfile
(C
: Character) is
713 Put
(Outfile
.all, C
);
714 end Put_Char_To_Outfile
;
716 -----------------------
717 -- Scan_Command_Line --
718 -----------------------
720 procedure Scan_Command_Line
is
723 procedure Check_Version_And_Help
is new Check_Version_And_Help_G
(Usage
);
725 -- Start of processing for Scan_Command_Line
728 -- First check for --version or --help
730 Check_Version_And_Help
("GNATPREP", "1996");
732 -- Now scan the other switches
734 GNAT
.Command_Line
.Initialize_Option_Scan
;
738 Switch
:= GNAT
.Command_Line
.Getopt
("D: a b c C r s T u v");
745 Process_Command_Line_Symbol_Definition
746 (S
=> GNAT
.Command_Line
.Parameter
);
749 Opt
.No_Deletion
:= True;
750 Opt
.Undefined_Symbols_Are_False
:= True;
753 Opt
.Blank_Deleted_Lines
:= True;
756 Opt
.Comment_Deleted_Lines
:= True;
759 Opt
.Replace_In_Comments
:= True;
762 Source_Ref_Pragma
:= True;
765 Opt
.List_Preprocessing_Symbols
:= True;
768 Unix_Line_Terminators
:= True;
771 Opt
.Undefined_Symbols_Are_False
:= True;
774 Opt
.Verbose_Mode
:= True;
777 Fail
("Invalid Switch: -" & Switch
);
781 when GNAT
.Command_Line
.Invalid_Switch
=>
782 Write_Str
("Invalid Switch: -");
783 Write_Line
(GNAT
.Command_Line
.Full_Switch
);
784 GNAT
.Command_Line
.Try_Help
;
789 -- Get the file names
793 S
: constant String := GNAT
.Command_Line
.Get_Argument
;
796 exit when S
'Length = 0;
798 Name_Len
:= S
'Length;
799 Name_Buffer
(1 .. Name_Len
) := S
;
801 if Infile_Name
= No_Name
then
802 Infile_Name
:= Name_Find
;
803 elsif Outfile_Name
= No_Name
then
804 Outfile_Name
:= Name_Find
;
805 elsif Deffile_Name
= No_Name
then
806 Deffile_Name
:= Name_Find
;
808 Fail
("too many arguments specified");
812 end Scan_Command_Line
;
821 Write_Line
("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
822 "infile outfile [deffile]");
824 Write_Line
(" infile Name of the input file");
825 Write_Line
(" outfile Name of the output file");
826 Write_Line
(" deffile Name of the definition file");
828 Write_Line
("gnatprep switches:");
829 Display_Usage_Version_And_Help
;
830 Write_Line
(" -b Replace preprocessor lines by blank lines");
831 Write_Line
(" -c Keep preprocessor lines as comments");
832 Write_Line
(" -C Do symbol replacements within comments");
833 Write_Line
(" -D Associate symbol with value");
834 Write_Line
(" -r Generate Source_Reference pragma");
835 Write_Line
(" -s Print a sorted list of symbol names and values");
836 Write_Line
(" -T Use LF as line terminators");
837 Write_Line
(" -u Treat undefined symbols as FALSE");
838 Write_Line
(" -v Verbose mode");