1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
9 -- Copyright (C) 2002-2014, 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
;
41 with Ada
.Command_Line
; use Ada
.Command_Line
;
42 with Ada
.Text_IO
; use Ada
.Text_IO
;
44 with GNAT
.Case_Util
; use GNAT
.Case_Util
;
45 with GNAT
.Command_Line
;
46 with GNAT
.Directory_Operations
; use GNAT
.Directory_Operations
;
48 with System
.OS_Lib
; use System
.OS_Lib
;
52 Copyright_Displayed
: Boolean := False;
53 -- Used to prevent multiple displays of the copyright notice
55 ------------------------
56 -- Argument Line Data --
57 ------------------------
59 Unix_Line_Terminators
: Boolean := False;
60 -- Set to True with option -T
62 type String_Array
is array (Boolean) of String_Access
;
63 Yes_No
: constant String_Array
:=
64 (False => new String'("YES"),
65 True => new String'("NO"));
67 Infile_Name
: Name_Id
:= No_Name
;
68 Outfile_Name
: Name_Id
:= No_Name
;
69 Deffile_Name
: Name_Id
:= No_Name
;
71 Output_Directory
: Name_Id
:= No_Name
;
72 -- Used when the specified output is an existing directory
74 Input_Directory
: Name_Id
:= No_Name
;
75 -- Used when the specified input and output are existing directories
77 Source_Ref_Pragma
: Boolean := False;
78 -- Record command line options (set if -r switch set)
80 Text_Outfile
: aliased Ada
.Text_IO
.File_Type
;
81 Outfile
: constant File_Access
:= Text_Outfile
'Access;
83 File_Name_Buffer_Initial_Size
: constant := 50;
84 File_Name_Buffer
: String_Access
:=
85 new String (1 .. File_Name_Buffer_Initial_Size
);
86 -- A buffer to build output file names from input file names
92 procedure Display_Copyright
;
93 -- Display the copyright notice
96 -- Null procedure, needed by instantiation of Scng below
98 package Scanner
is new Scng
102 Errutil
.Error_Msg_SC
,
103 Errutil
.Error_Msg_SP
,
105 -- The scanner for the preprocessor
107 function Is_ASCII_Letter
(C
: Character) return Boolean;
108 -- True if C is in 'a' .. 'z' or in 'A' .. 'Z'
110 procedure Double_File_Name_Buffer
;
111 -- Double the size of the file name buffer
113 procedure Preprocess_Infile_Name
;
114 -- When the specified output is a directory, preprocess the infile name
115 -- for symbol substitution, to get the output file name.
117 procedure Process_Files
;
118 -- Process the single input file or all the files in the directory tree
119 -- rooted at the input directory.
121 procedure Process_Command_Line_Symbol_Definition
(S
: String);
122 -- Process a -D switch on the command line
124 procedure Put_Char_To_Outfile
(C
: Character);
125 -- Output one character to the output file. Used to initialize the
128 procedure New_EOL_To_Outfile
;
129 -- Output a new line to the output file. Used to initialize the
132 procedure Scan_Command_Line
;
133 -- Scan the switches and the file names
138 -----------------------
139 -- Display_Copyright --
140 -----------------------
142 procedure Display_Copyright
is
144 if not Copyright_Displayed
then
145 Display_Version
("GNAT Preprocessor", "1996");
146 Copyright_Displayed
:= True;
148 end Display_Copyright
;
150 -----------------------------
151 -- Double_File_Name_Buffer --
152 -----------------------------
154 procedure Double_File_Name_Buffer
is
155 New_Buffer
: constant String_Access
:=
156 new String (1 .. 2 * File_Name_Buffer
'Length);
158 New_Buffer
(File_Name_Buffer
'Range) := File_Name_Buffer
.all;
159 Free
(File_Name_Buffer
);
160 File_Name_Buffer
:= New_Buffer
;
161 end Double_File_Name_Buffer
;
167 procedure Gnatprep
is
169 -- Do some initializations (order is important here)
176 -- Initialize the preprocessor
179 (Error_Msg
=> Errutil
.Error_Msg
'Access,
180 Scan
=> Scanner
.Scan
'Access,
181 Set_Ignore_Errors
=> Errutil
.Set_Ignore_Errors
'Access,
182 Put_Char
=> Put_Char_To_Outfile
'Access,
183 New_EOL
=> New_EOL_To_Outfile
'Access);
185 -- Set the scanner characteristics for the preprocessor
187 Scanner
.Set_Special_Character
('#');
188 Scanner
.Set_Special_Character
('$');
189 Scanner
.Set_End_Of_Line_As_Token
(True);
191 -- Initialize the mapping table of symbols to values
193 Prep
.Symbol_Table
.Init
(Prep
.Mapping
);
195 -- Parse the switches and arguments
199 if Opt
.Verbose_Mode
then
203 -- Test we had all the arguments needed
205 if Infile_Name
= No_Name
then
207 -- No input file specified, just output the usage and exit
209 if Argument_Count
= 0 then
212 GNAT
.Command_Line
.Try_Help
;
217 elsif Outfile_Name
= No_Name
then
219 -- No output file specified, exit
221 GNAT
.Command_Line
.Try_Help
;
225 -- If a pragma Source_File_Name, we need to keep line numbers. So, if
226 -- the deleted lines are not put as comment, we must output them as
229 if Source_Ref_Pragma
and (not Opt
.Comment_Deleted_Lines
) then
230 Opt
.Blank_Deleted_Lines
:= True;
233 -- If we have a definition file, parse it
235 if Deffile_Name
/= No_Name
then
237 Deffile
: Source_File_Index
;
241 Deffile
:= Sinput
.C
.Load_File
(Get_Name_String
(Deffile_Name
));
243 -- Set Main_Source_File to the definition file for the benefit of
246 Sinput
.Main_Source_File
:= Deffile
;
248 if Deffile
= No_Source_File
then
249 Fail
("unable to find definition file """
250 & Get_Name_String
(Deffile_Name
)
254 Scanner
.Initialize_Scanner
(Deffile
);
260 -- If there are errors in the definition file, output them and exit
262 if Total_Errors_Detected
> 0 then
263 Errutil
.Finalize
(Source_Type
=> "definition");
264 Fail
("errors in definition file """
265 & Get_Name_String
(Deffile_Name
)
269 -- If -s switch was specified, print a sorted list of symbol names and
272 if Opt
.List_Preprocessing_Symbols
then
273 Prep
.List_Symbols
(Foreword
=> "");
276 Output_Directory
:= No_Name
;
277 Input_Directory
:= No_Name
;
279 -- Check if the specified output is an existing directory
281 if Is_Directory
(Get_Name_String
(Outfile_Name
)) then
282 Output_Directory
:= Outfile_Name
;
284 -- As the output is an existing directory, check if the input too
287 if Is_Directory
(Get_Name_String
(Infile_Name
)) then
288 Input_Directory
:= Infile_Name
;
292 -- And process the single input or the files in the directory tree
293 -- rooted at the input directory.
298 ---------------------
299 -- Is_ASCII_Letter --
300 ---------------------
302 function Is_ASCII_Letter
(C
: Character) return Boolean is
304 return C
in 'A' .. 'Z' or else C
in 'a' .. 'z';
307 ------------------------
308 -- New_EOL_To_Outfile --
309 ------------------------
311 procedure New_EOL_To_Outfile
is
313 New_Line
(Outfile
.all);
314 end New_EOL_To_Outfile
;
320 procedure Post_Scan
is
325 ----------------------------
326 -- Preprocess_Infile_Name --
327 ----------------------------
329 procedure Preprocess_Infile_Name
is
337 -- Initialize the buffer with the name of the input file
339 Get_Name_String
(Infile_Name
);
342 while File_Name_Buffer
'Length < Len
loop
343 Double_File_Name_Buffer
;
346 File_Name_Buffer
(1 .. Len
) := Name_Buffer
(1 .. Len
);
348 -- Look for possible symbols in the file name
351 while First
< Len
loop
353 -- A symbol starts with a dollar sign followed by a letter
355 if File_Name_Buffer
(First
) = '$' and then
356 Is_ASCII_Letter
(File_Name_Buffer
(First
+ 1))
360 -- Find the last letter of the symbol
362 while Last
< Len
and then
363 Is_ASCII_Letter
(File_Name_Buffer
(Last
+ 1))
368 -- Get the symbol name id
370 Name_Len
:= Last
- First
;
371 Name_Buffer
(1 .. Name_Len
) :=
372 File_Name_Buffer
(First
+ 1 .. Last
);
373 To_Lower
(Name_Buffer
(1 .. Name_Len
));
376 -- And look for this symbol name in the symbol table
378 for Index
in 1 .. Symbol_Table
.Last
(Mapping
) loop
379 Data
:= Mapping
.Table
(Index
);
381 if Data
.Symbol
= Symbol
then
383 -- We found the symbol. If its value is not a string,
384 -- replace the symbol in the file name with the value of
387 if not Data
.Is_A_String
then
388 String_To_Name_Buffer
(Data
.Value
);
391 Sym_Len
: constant Positive := Last
- First
+ 1;
392 Offset
: constant Integer := Name_Len
- Sym_Len
;
393 New_Len
: constant Natural := Len
+ Offset
;
396 while New_Len
> File_Name_Buffer
'Length loop
397 Double_File_Name_Buffer
;
400 File_Name_Buffer
(Last
+ 1 + Offset
.. New_Len
) :=
401 File_Name_Buffer
(Last
+ 1 .. Len
);
403 Last
:= Last
+ Offset
;
404 File_Name_Buffer
(First
.. Last
) :=
405 Name_Buffer
(1 .. Name_Len
);
413 -- Skip over the symbol name or its value: we are not checking
414 -- for another symbol name in the value.
423 -- We now have the output file name in the buffer. Get the output
424 -- path and put it in Outfile_Name.
426 Get_Name_String
(Output_Directory
);
427 Add_Char_To_Name_Buffer
(Directory_Separator
);
428 Add_Str_To_Name_Buffer
(File_Name_Buffer
(1 .. Len
));
429 Outfile_Name
:= Name_Find
;
430 end Preprocess_Infile_Name
;
432 --------------------------------------------
433 -- Process_Command_Line_Symbol_Definition --
434 --------------------------------------------
436 procedure Process_Command_Line_Symbol_Definition
(S
: String) is
441 -- Check the symbol definition and get the symbol and its value.
442 -- Fail if symbol definition is illegal.
444 Check_Command_Line_Symbol_Definition
(S
, Data
);
446 Symbol
:= Index_Of
(Data
.Symbol
);
448 -- If symbol does not already exist, create a new entry in the mapping
451 if Symbol
= No_Symbol
then
452 Symbol_Table
.Increment_Last
(Mapping
);
453 Symbol
:= Symbol_Table
.Last
(Mapping
);
456 Mapping
.Table
(Symbol
) := Data
;
457 end Process_Command_Line_Symbol_Definition
;
463 procedure Process_Files
is
465 procedure Process_One_File
;
466 -- Process input file Infile_Name and put the result in file
469 procedure Recursive_Process
(In_Dir
: String; Out_Dir
: String);
470 -- Process recursively files in In_Dir. Results go to Out_Dir
472 ----------------------
473 -- Process_One_File --
474 ----------------------
476 procedure Process_One_File
is
477 Infile
: Source_File_Index
;
480 pragma Warnings
(Off
, Modified
);
483 -- Create the output file (fails if this does not work)
487 (File
=> Text_Outfile
,
489 Name
=> Get_Name_String
(Outfile_Name
),
490 Form
=> "Text_Translation=" &
491 Yes_No
(Unix_Line_Terminators
).all);
496 ("unable to create output file """
497 & Get_Name_String
(Outfile_Name
)
501 -- Load the input file
503 Infile
:= Sinput
.C
.Load_File
(Get_Name_String
(Infile_Name
));
505 if Infile
= No_Source_File
then
506 Fail
("unable to find input file """
507 & Get_Name_String
(Infile_Name
)
511 -- Set Main_Source_File to the input file for the benefit of
514 Sinput
.Main_Source_File
:= Infile
;
516 Scanner
.Initialize_Scanner
(Infile
);
518 -- Output the pragma Source_Reference if asked to
520 if Source_Ref_Pragma
then
523 "pragma Source_Reference (1, """ &
524 Get_Name_String
(Sinput
.Full_File_Name
(Infile
)) & """);");
527 -- Preprocess the input file
529 Prep
.Preprocess
(Modified
);
531 -- In verbose mode, if there is no error, report it
533 if Opt
.Verbose_Mode
and then Total_Errors_Detected
= 0 then
534 Errutil
.Finalize
(Source_Type
=> "input");
537 -- If we had some errors, delete the output file, and report them
539 if Total_Errors_Detected
> 0 then
540 if Outfile
/= Standard_Output
then
541 Delete
(Text_Outfile
);
544 Errutil
.Finalize
(Source_Type
=> "input");
548 -- Otherwise, close the output file, and we are done
550 elsif Outfile
/= Standard_Output
then
551 Close
(Text_Outfile
);
553 end Process_One_File
;
555 -----------------------
556 -- Recursive_Process --
557 -----------------------
559 procedure Recursive_Process
(In_Dir
: String; Out_Dir
: String) is
561 Name
: String (1 .. 255);
563 In_Dir_Name
: Name_Id
;
564 Out_Dir_Name
: Name_Id
;
566 procedure Set_Directory_Names
;
567 -- Establish or reestablish the current input and output directories
569 -------------------------
570 -- Set_Directory_Names --
571 -------------------------
573 procedure Set_Directory_Names
is
575 Input_Directory
:= In_Dir_Name
;
576 Output_Directory
:= Out_Dir_Name
;
577 end Set_Directory_Names
;
579 -- Start of processing for Recursive_Process
582 -- Open the current input directory
585 Open
(Dir_In
, In_Dir
);
588 when Directory_Error
=>
589 Fail
("could not read directory " & In_Dir
);
592 -- Set the new input and output directory names
594 Name_Len
:= In_Dir
'Length;
595 Name_Buffer
(1 .. Name_Len
) := In_Dir
;
596 In_Dir_Name
:= Name_Find
;
597 Name_Len
:= Out_Dir
'Length;
598 Name_Buffer
(1 .. Name_Len
) := Out_Dir
;
599 Out_Dir_Name
:= Name_Find
;
603 -- Traverse the input directory
605 Read
(Dir_In
, Name
, Last
);
608 if Name
(1 .. Last
) /= "." and then Name
(1 .. Last
) /= ".." then
610 Input
: constant String :=
611 In_Dir
& Directory_Separator
& Name
(1 .. Last
);
612 Output
: constant String :=
613 Out_Dir
& Directory_Separator
& Name
(1 .. Last
);
616 -- If input is an ordinary file, process it
618 if Is_Regular_File
(Input
) then
619 -- First get the output file name
622 Name_Buffer
(1 .. Name_Len
) := Name
(1 .. Last
);
623 Infile_Name
:= Name_Find
;
624 Preprocess_Infile_Name
;
626 -- Set the input file name and process the file
628 Name_Len
:= Input
'Length;
629 Name_Buffer
(1 .. Name_Len
) := Input
;
630 Infile_Name
:= Name_Find
;
633 elsif Is_Directory
(Input
) then
634 -- Input is a directory. If the corresponding output
635 -- directory does not already exist, create it.
637 if not Is_Directory
(Output
) then
639 Make_Dir
(Dir_Name
=> Output
);
642 when Directory_Error
=>
643 Fail
("could not create directory """
649 -- And process this new input directory
651 Recursive_Process
(Input
, Output
);
653 -- Reestablish the input and output directory names
654 -- that have been modified by the recursive call.
661 end Recursive_Process
;
663 -- Start of processing for Process_Files
666 if Output_Directory
= No_Name
then
668 -- If the output is not a directory, fail if the input is
669 -- an existing directory, to avoid possible problems.
671 if Is_Directory
(Get_Name_String
(Infile_Name
)) then
672 Fail
("input file """ & Get_Name_String
(Infile_Name
) &
673 """ is a directory");
676 -- Just process the single input file
680 elsif Input_Directory
= No_Name
then
682 -- Get the output file name from the input file name, and process
683 -- the single input file.
685 Preprocess_Infile_Name
;
689 -- Recursively process files in the directory tree rooted at the
693 (In_Dir
=> Get_Name_String
(Input_Directory
),
694 Out_Dir
=> Get_Name_String
(Output_Directory
));
698 -------------------------
699 -- Put_Char_To_Outfile --
700 -------------------------
702 procedure Put_Char_To_Outfile
(C
: Character) is
704 Put
(Outfile
.all, C
);
705 end Put_Char_To_Outfile
;
707 -----------------------
708 -- Scan_Command_Line --
709 -----------------------
711 procedure Scan_Command_Line
is
714 procedure Check_Version_And_Help
is new Check_Version_And_Help_G
(Usage
);
716 -- Start of processing for Scan_Command_Line
719 -- First check for --version or --help
721 Check_Version_And_Help
("GNATPREP", "1996");
723 -- Now scan the other switches
725 GNAT
.Command_Line
.Initialize_Option_Scan
;
729 Switch
:= GNAT
.Command_Line
.Getopt
("D: a b c C r s T u v");
737 Process_Command_Line_Symbol_Definition
738 (S
=> GNAT
.Command_Line
.Parameter
);
741 Opt
.No_Deletion
:= True;
742 Opt
.Undefined_Symbols_Are_False
:= True;
745 Opt
.Blank_Deleted_Lines
:= True;
748 Opt
.Comment_Deleted_Lines
:= True;
751 Opt
.Replace_In_Comments
:= True;
754 Source_Ref_Pragma
:= True;
757 Opt
.List_Preprocessing_Symbols
:= True;
760 Unix_Line_Terminators
:= True;
763 Opt
.Undefined_Symbols_Are_False
:= True;
766 Opt
.Verbose_Mode
:= True;
769 Fail
("Invalid Switch: -" & Switch
);
773 when GNAT
.Command_Line
.Invalid_Switch
=>
774 Write_Str
("Invalid Switch: -");
775 Write_Line
(GNAT
.Command_Line
.Full_Switch
);
776 GNAT
.Command_Line
.Try_Help
;
781 -- Get the file names
785 S
: constant String := GNAT
.Command_Line
.Get_Argument
;
788 exit when S
'Length = 0;
790 Name_Len
:= S
'Length;
791 Name_Buffer
(1 .. Name_Len
) := S
;
793 if Infile_Name
= No_Name
then
794 Infile_Name
:= Name_Find
;
795 elsif Outfile_Name
= No_Name
then
796 Outfile_Name
:= Name_Find
;
797 elsif Deffile_Name
= No_Name
then
798 Deffile_Name
:= Name_Find
;
800 Fail
("too many arguments specified");
804 end Scan_Command_Line
;
813 Write_Line
("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
814 "infile outfile [deffile]");
816 Write_Line
(" infile Name of the input file");
817 Write_Line
(" outfile Name of the output file");
818 Write_Line
(" deffile Name of the definition file");
820 Write_Line
("gnatprep switches:");
821 Display_Usage_Version_And_Help
;
822 Write_Line
(" -b Replace preprocessor lines by blank lines");
823 Write_Line
(" -c Keep preprocessor lines as comments");
824 Write_Line
(" -C Do symbol replacements within comments");
825 Write_Line
(" -D Associate symbol with value");
826 Write_Line
(" -r Generate Source_Reference pragma");
827 Write_Line
(" -s Print a sorted list of symbol names and values");
828 Write_Line
(" -T Use LF as line terminators");
829 Write_Line
(" -u Treat undefined symbols as FALSE");
830 Write_Line
(" -v Verbose mode");