1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
9 -- Copyright (C) 2002-2016, 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
);
256 -- Parse the definition file without "replace in comments"
259 Replace
: constant Boolean := Opt
.Replace_In_Comments
;
261 Opt
.Replace_In_Comments
:= False;
263 Opt
.Replace_In_Comments
:= Replace
;
268 -- If there are errors in the definition file, output them and exit
270 if Total_Errors_Detected
> 0 then
271 Errutil
.Finalize
(Source_Type
=> "definition");
272 Fail
("errors in definition file """
273 & Get_Name_String
(Deffile_Name
)
277 -- If -s switch was specified, print a sorted list of symbol names and
280 if Opt
.List_Preprocessing_Symbols
then
281 Prep
.List_Symbols
(Foreword
=> "");
284 Output_Directory
:= No_Name
;
285 Input_Directory
:= No_Name
;
287 -- Check if the specified output is an existing directory
289 if Is_Directory
(Get_Name_String
(Outfile_Name
)) then
290 Output_Directory
:= Outfile_Name
;
292 -- As the output is an existing directory, check if the input too
295 if Is_Directory
(Get_Name_String
(Infile_Name
)) then
296 Input_Directory
:= Infile_Name
;
300 -- And process the single input or the files in the directory tree
301 -- rooted at the input directory.
306 ---------------------
307 -- Is_ASCII_Letter --
308 ---------------------
310 function Is_ASCII_Letter
(C
: Character) return Boolean is
312 return C
in 'A' .. 'Z' or else C
in 'a' .. 'z';
315 ------------------------
316 -- New_EOL_To_Outfile --
317 ------------------------
319 procedure New_EOL_To_Outfile
is
321 New_Line
(Outfile
.all);
322 end New_EOL_To_Outfile
;
328 procedure Post_Scan
is
333 ----------------------------
334 -- Preprocess_Infile_Name --
335 ----------------------------
337 procedure Preprocess_Infile_Name
is
345 -- Initialize the buffer with the name of the input file
347 Get_Name_String
(Infile_Name
);
350 while File_Name_Buffer
'Length < Len
loop
351 Double_File_Name_Buffer
;
354 File_Name_Buffer
(1 .. Len
) := Name_Buffer
(1 .. Len
);
356 -- Look for possible symbols in the file name
359 while First
< Len
loop
361 -- A symbol starts with a dollar sign followed by a letter
363 if File_Name_Buffer
(First
) = '$' and then
364 Is_ASCII_Letter
(File_Name_Buffer
(First
+ 1))
368 -- Find the last letter of the symbol
370 while Last
< Len
and then
371 Is_ASCII_Letter
(File_Name_Buffer
(Last
+ 1))
376 -- Get the symbol name id
378 Name_Len
:= Last
- First
;
379 Name_Buffer
(1 .. Name_Len
) :=
380 File_Name_Buffer
(First
+ 1 .. Last
);
381 To_Lower
(Name_Buffer
(1 .. Name_Len
));
384 -- And look for this symbol name in the symbol table
386 for Index
in 1 .. Symbol_Table
.Last
(Mapping
) loop
387 Data
:= Mapping
.Table
(Index
);
389 if Data
.Symbol
= Symbol
then
391 -- We found the symbol. If its value is not a string,
392 -- replace the symbol in the file name with the value of
395 if not Data
.Is_A_String
then
396 String_To_Name_Buffer
(Data
.Value
);
399 Sym_Len
: constant Positive := Last
- First
+ 1;
400 Offset
: constant Integer := Name_Len
- Sym_Len
;
401 New_Len
: constant Natural := Len
+ Offset
;
404 while New_Len
> File_Name_Buffer
'Length loop
405 Double_File_Name_Buffer
;
408 File_Name_Buffer
(Last
+ 1 + Offset
.. New_Len
) :=
409 File_Name_Buffer
(Last
+ 1 .. Len
);
411 Last
:= Last
+ Offset
;
412 File_Name_Buffer
(First
.. Last
) :=
413 Name_Buffer
(1 .. Name_Len
);
421 -- Skip over the symbol name or its value: we are not checking
422 -- for another symbol name in the value.
431 -- We now have the output file name in the buffer. Get the output
432 -- path and put it in Outfile_Name.
434 Get_Name_String
(Output_Directory
);
435 Add_Char_To_Name_Buffer
(Directory_Separator
);
436 Add_Str_To_Name_Buffer
(File_Name_Buffer
(1 .. Len
));
437 Outfile_Name
:= Name_Find
;
438 end Preprocess_Infile_Name
;
440 --------------------------------------------
441 -- Process_Command_Line_Symbol_Definition --
442 --------------------------------------------
444 procedure Process_Command_Line_Symbol_Definition
(S
: String) is
449 -- Check the symbol definition and get the symbol and its value.
450 -- Fail if symbol definition is illegal.
452 Check_Command_Line_Symbol_Definition
(S
, Data
);
454 Symbol
:= Index_Of
(Data
.Symbol
);
456 -- If symbol does not already exist, create a new entry in the mapping
459 if Symbol
= No_Symbol
then
460 Symbol_Table
.Increment_Last
(Mapping
);
461 Symbol
:= Symbol_Table
.Last
(Mapping
);
464 Mapping
.Table
(Symbol
) := Data
;
465 end Process_Command_Line_Symbol_Definition
;
471 procedure Process_Files
is
473 procedure Process_One_File
;
474 -- Process input file Infile_Name and put the result in file
477 procedure Recursive_Process
(In_Dir
: String; Out_Dir
: String);
478 -- Process recursively files in In_Dir. Results go to Out_Dir
480 ----------------------
481 -- Process_One_File --
482 ----------------------
484 procedure Process_One_File
is
485 Infile
: Source_File_Index
;
488 pragma Warnings
(Off
, Modified
);
491 -- Create the output file (fails if this does not work)
495 (File
=> Text_Outfile
,
497 Name
=> Get_Name_String
(Outfile_Name
),
498 Form
=> "Text_Translation=" &
499 Yes_No
(Unix_Line_Terminators
).all);
504 ("unable to create output file """
505 & Get_Name_String
(Outfile_Name
)
509 -- Load the input file
511 Infile
:= Sinput
.C
.Load_File
(Get_Name_String
(Infile_Name
));
513 if Infile
= No_Source_File
then
514 Fail
("unable to find input file """
515 & Get_Name_String
(Infile_Name
)
519 -- Set Main_Source_File to the input file for the benefit of
522 Sinput
.Main_Source_File
:= Infile
;
524 Scanner
.Initialize_Scanner
(Infile
);
526 -- Output the pragma Source_Reference if asked to
528 if Source_Ref_Pragma
then
531 "pragma Source_Reference (1, """ &
532 Get_Name_String
(Sinput
.Full_File_Name
(Infile
)) & """);");
535 -- Preprocess the input file
537 Prep
.Preprocess
(Modified
);
539 -- In verbose mode, if there is no error, report it
541 if Opt
.Verbose_Mode
and then Total_Errors_Detected
= 0 then
542 Errutil
.Finalize
(Source_Type
=> "input");
545 -- If we had some errors, delete the output file, and report them
547 if Total_Errors_Detected
> 0 then
548 if Outfile
/= Standard_Output
then
549 Delete
(Text_Outfile
);
552 Errutil
.Finalize
(Source_Type
=> "input");
556 -- Otherwise, close the output file, and we are done
558 elsif Outfile
/= Standard_Output
then
559 Close
(Text_Outfile
);
561 end Process_One_File
;
563 -----------------------
564 -- Recursive_Process --
565 -----------------------
567 procedure Recursive_Process
(In_Dir
: String; Out_Dir
: String) is
569 Name
: String (1 .. 255);
571 In_Dir_Name
: Name_Id
;
572 Out_Dir_Name
: Name_Id
;
574 procedure Set_Directory_Names
;
575 -- Establish or reestablish the current input and output directories
577 -------------------------
578 -- Set_Directory_Names --
579 -------------------------
581 procedure Set_Directory_Names
is
583 Input_Directory
:= In_Dir_Name
;
584 Output_Directory
:= Out_Dir_Name
;
585 end Set_Directory_Names
;
587 -- Start of processing for Recursive_Process
590 -- Open the current input directory
593 Open
(Dir_In
, In_Dir
);
596 when Directory_Error
=>
597 Fail
("could not read directory " & In_Dir
);
600 -- Set the new input and output directory names
602 Name_Len
:= In_Dir
'Length;
603 Name_Buffer
(1 .. Name_Len
) := In_Dir
;
604 In_Dir_Name
:= Name_Find
;
605 Name_Len
:= Out_Dir
'Length;
606 Name_Buffer
(1 .. Name_Len
) := Out_Dir
;
607 Out_Dir_Name
:= Name_Find
;
611 -- Traverse the input directory
613 Read
(Dir_In
, Name
, Last
);
616 if Name
(1 .. Last
) /= "." and then Name
(1 .. Last
) /= ".." then
618 Input
: constant String :=
619 In_Dir
& Directory_Separator
& Name
(1 .. Last
);
620 Output
: constant String :=
621 Out_Dir
& Directory_Separator
& Name
(1 .. Last
);
624 -- If input is an ordinary file, process it
626 if Is_Regular_File
(Input
) then
627 -- First get the output file name
630 Name_Buffer
(1 .. Name_Len
) := Name
(1 .. Last
);
631 Infile_Name
:= Name_Find
;
632 Preprocess_Infile_Name
;
634 -- Set the input file name and process the file
636 Name_Len
:= Input
'Length;
637 Name_Buffer
(1 .. Name_Len
) := Input
;
638 Infile_Name
:= Name_Find
;
641 elsif Is_Directory
(Input
) then
642 -- Input is a directory. If the corresponding output
643 -- directory does not already exist, create it.
645 if not Is_Directory
(Output
) then
647 Make_Dir
(Dir_Name
=> Output
);
650 when Directory_Error
=>
651 Fail
("could not create directory """
657 -- And process this new input directory
659 Recursive_Process
(Input
, Output
);
661 -- Reestablish the input and output directory names
662 -- that have been modified by the recursive call.
669 end Recursive_Process
;
671 -- Start of processing for Process_Files
674 if Output_Directory
= No_Name
then
676 -- If the output is not a directory, fail if the input is
677 -- an existing directory, to avoid possible problems.
679 if Is_Directory
(Get_Name_String
(Infile_Name
)) then
680 Fail
("input file """ & Get_Name_String
(Infile_Name
) &
681 """ is a directory");
684 -- Just process the single input file
688 elsif Input_Directory
= No_Name
then
690 -- Get the output file name from the input file name, and process
691 -- the single input file.
693 Preprocess_Infile_Name
;
697 -- Recursively process files in the directory tree rooted at the
701 (In_Dir
=> Get_Name_String
(Input_Directory
),
702 Out_Dir
=> Get_Name_String
(Output_Directory
));
706 -------------------------
707 -- Put_Char_To_Outfile --
708 -------------------------
710 procedure Put_Char_To_Outfile
(C
: Character) is
712 Put
(Outfile
.all, C
);
713 end Put_Char_To_Outfile
;
715 -----------------------
716 -- Scan_Command_Line --
717 -----------------------
719 procedure Scan_Command_Line
is
722 procedure Check_Version_And_Help
is new Check_Version_And_Help_G
(Usage
);
724 -- Start of processing for Scan_Command_Line
727 -- First check for --version or --help
729 Check_Version_And_Help
("GNATPREP", "1996");
731 -- Now scan the other switches
733 GNAT
.Command_Line
.Initialize_Option_Scan
;
737 Switch
:= GNAT
.Command_Line
.Getopt
("D: a b c C r s T u v");
744 Process_Command_Line_Symbol_Definition
745 (S
=> GNAT
.Command_Line
.Parameter
);
748 Opt
.No_Deletion
:= True;
749 Opt
.Undefined_Symbols_Are_False
:= True;
752 Opt
.Blank_Deleted_Lines
:= True;
755 Opt
.Comment_Deleted_Lines
:= True;
758 Opt
.Replace_In_Comments
:= True;
761 Source_Ref_Pragma
:= True;
764 Opt
.List_Preprocessing_Symbols
:= True;
767 Unix_Line_Terminators
:= True;
770 Opt
.Undefined_Symbols_Are_False
:= True;
773 Opt
.Verbose_Mode
:= True;
776 Fail
("Invalid Switch: -" & Switch
);
780 when GNAT
.Command_Line
.Invalid_Switch
=>
781 Write_Str
("Invalid Switch: -");
782 Write_Line
(GNAT
.Command_Line
.Full_Switch
);
783 GNAT
.Command_Line
.Try_Help
;
788 -- Get the file names
792 S
: constant String := GNAT
.Command_Line
.Get_Argument
;
795 exit when S
'Length = 0;
797 Name_Len
:= S
'Length;
798 Name_Buffer
(1 .. Name_Len
) := S
;
800 if Infile_Name
= No_Name
then
801 Infile_Name
:= Name_Find
;
802 elsif Outfile_Name
= No_Name
then
803 Outfile_Name
:= Name_Find
;
804 elsif Deffile_Name
= No_Name
then
805 Deffile_Name
:= Name_Find
;
807 Fail
("too many arguments specified");
811 end Scan_Command_Line
;
820 Write_Line
("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
821 "infile outfile [deffile]");
823 Write_Line
(" infile Name of the input file");
824 Write_Line
(" outfile Name of the output file");
825 Write_Line
(" deffile Name of the definition file");
827 Write_Line
("gnatprep switches:");
828 Display_Usage_Version_And_Help
;
829 Write_Line
(" -b Replace preprocessor lines by blank lines");
830 Write_Line
(" -c Keep preprocessor lines as comments");
831 Write_Line
(" -C Do symbol replacements within comments");
832 Write_Line
(" -D Associate symbol with value");
833 Write_Line
(" -r Generate Source_Reference pragma");
834 Write_Line
(" -s Print a sorted list of symbol names and values");
835 Write_Line
(" -T Use LF as line terminators");
836 Write_Line
(" -u Treat undefined symbols as FALSE");
837 Write_Line
(" -v Verbose mode");