1 ------------------------------------------------------------------------------
3 -- GNAT COMPILER COMPONENTS --
9 -- Copyright (C) 2002-2007, 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 2, 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 COPYING. If not, write --
19 -- to the Free Software Foundation, 51 Franklin Street, Fifth Floor, --
20 -- Boston, MA 02110-1301, USA. --
22 -- GNAT was originally developed by the GNAT team at New York University. --
23 -- Extensive contributions were provided by Ada Core Technologies Inc. --
25 ------------------------------------------------------------------------------
28 with Err_Vars; use Err_Vars;
30 with Gnatvsn; use Gnatvsn;
31 with Namet; use Namet;
33 with Osint; use Osint;
34 with Output; use Output;
39 with Stringt; use Stringt;
40 with Types; use Types;
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 Infile_Name : Name_Id := No_Name;
60 Outfile_Name : Name_Id := No_Name;
61 Deffile_Name : Name_Id := No_Name;
63 Output_Directory : Name_Id := No_Name;
64 -- Used when the specified output is an existing directory
66 Input_Directory : Name_Id := No_Name;
67 -- Used when the specified input and output are existing directories
69 Source_Ref_Pragma : Boolean := False;
70 -- Record command line options (set if -r switch set)
72 Text_Outfile : aliased Ada.Text_IO.File_Type;
73 Outfile : constant File_Access := Text_Outfile'Access;
75 File_Name_Buffer_Initial_Size : constant := 50;
76 File_Name_Buffer : String_Access :=
77 new String (1 .. File_Name_Buffer_Initial_Size);
78 -- A buffer to build output file names from input file names
84 procedure Display_Copyright;
85 -- Display the copyright notice
87 procedure Obsolescent_Check (S : Source_Ptr);
88 -- Null procedure, needed by instantiation of Scng below
91 -- Null procedure, needed by instantiation of Scng below
93 package Scanner is new Scng
101 -- The scanner for the preprocessor
103 function Is_ASCII_Letter (C : Character) return Boolean;
104 -- True if C is in 'a' .. 'z' or in 'A' .. 'Z'
106 procedure Double_File_Name_Buffer;
107 -- Double the size of the file name buffer
109 procedure Preprocess_Infile_Name;
110 -- When the specified output is a directory, preprocess the infile name
111 -- for symbol substitution, to get the output file name.
113 procedure Process_Files;
114 -- Process the single input file or all the files in the directory tree
115 -- rooted at the input directory.
117 procedure Process_Command_Line_Symbol_Definition (S : String);
118 -- Process a -D switch on the command line
120 procedure Put_Char_To_Outfile (C : Character);
121 -- Output one character to the output file. Used to initialize the
124 procedure New_EOL_To_Outfile;
125 -- Output a new line to the output file. Used to initialize the
128 procedure Scan_Command_Line;
129 -- Scan the switches and the file names
134 -----------------------
135 -- Display_Copyright --
136 -----------------------
138 procedure Display_Copyright is
140 if not Copyright_Displayed then
141 Write_Line ("GNAT Preprocessor " & Gnatvsn.Gnat_Version_String);
142 Write_Line ("Copyright 1996-" &
144 ", Free Software Foundation, Inc.");
145 Copyright_Displayed := True;
147 end Display_Copyright;
149 -----------------------------
150 -- Double_File_Name_Buffer --
151 -----------------------------
153 procedure Double_File_Name_Buffer is
154 New_Buffer : constant String_Access :=
155 new String (1 .. 2 * File_Name_Buffer'Length);
157 New_Buffer (File_Name_Buffer'Range) := File_Name_Buffer.all;
158 Free (File_Name_Buffer);
159 File_Name_Buffer := New_Buffer;
160 end Double_File_Name_Buffer;
166 procedure Gnatprep is
168 -- Do some initializations (order is important here!)
175 -- Initialize the preprocessor
178 (Error_Msg => Errutil.Error_Msg'Access,
179 Scan => Scanner.Scan'Access,
180 Set_Ignore_Errors => Errutil.Set_Ignore_Errors'Access,
181 Put_Char => Put_Char_To_Outfile'Access,
182 New_EOL => New_EOL_To_Outfile'Access);
184 -- Set the scanner characteristics for the preprocessor
186 Scanner.Set_Special_Character ('#');
187 Scanner.Set_Special_Character ('$');
188 Scanner.Set_End_Of_Line_As_Token (True);
190 -- Initialize the mapping table of symbols to values
192 Prep.Symbol_Table.Init (Prep.Mapping);
194 -- Parse the switches and arguments
198 if Opt.Verbose_Mode then
202 -- Test we had all the arguments needed
204 if Infile_Name = No_Name then
206 -- No input file specified, just output the usage and exit
211 elsif Outfile_Name = No_Name then
213 -- No output file specified, just output the usage and exit
219 -- If a pragma Source_File_Name, we need to keep line numbers. So, if
220 -- the deleted lines are not put as comment, we must output them as
223 if Source_Ref_Pragma and (not Opt.Comment_Deleted_Lines) then
224 Opt.Blank_Deleted_Lines := True;
227 -- If we have a definition file, parse it
229 if Deffile_Name /= No_Name then
231 Deffile : Source_File_Index;
235 Deffile := Sinput.C.Load_File (Get_Name_String (Deffile_Name));
237 -- Set Main_Source_File to the definition file for the benefit of
240 Sinput.Main_Source_File := Deffile;
242 if Deffile = No_Source_File then
243 Fail ("unable to find definition file """,
244 Get_Name_String (Deffile_Name),
248 Scanner.Initialize_Scanner (Deffile);
254 -- If there are errors in the definition file, output them and exit
256 if Total_Errors_Detected > 0 then
257 Errutil.Finalize (Source_Type => "definition");
258 Fail ("errors in definition file """,
259 Get_Name_String (Deffile_Name), """");
262 -- If -s switch was specified, print a sorted list of symbol names and
265 if Opt.List_Preprocessing_Symbols then
266 Prep.List_Symbols (Foreword => "");
269 Output_Directory := No_Name;
270 Input_Directory := No_Name;
272 -- Check if the specified output is an existing directory
274 if Is_Directory (Get_Name_String (Outfile_Name)) then
275 Output_Directory := Outfile_Name;
277 -- As the output is an existing directory, check if the input too
280 if Is_Directory (Get_Name_String (Infile_Name)) then
281 Input_Directory := Infile_Name;
285 -- And process the single input or the files in the directory tree
286 -- rooted at the input directory.
291 ---------------------
292 -- Is_ASCII_Letter --
293 ---------------------
295 function Is_ASCII_Letter (C : Character) return Boolean is
297 return C in 'A' .. 'Z' or else C in 'a' .. 'z';
300 ------------------------
301 -- New_EOL_To_Outfile --
302 ------------------------
304 procedure New_EOL_To_Outfile is
306 New_Line (Outfile.all);
307 end New_EOL_To_Outfile;
309 -----------------------
310 -- Obsolescent_Check --
311 -----------------------
313 procedure Obsolescent_Check (S : Source_Ptr) is
314 pragma Warnings (Off, S);
317 end Obsolescent_Check;
323 procedure Post_Scan is
328 ----------------------------
329 -- Preprocess_Infile_Name --
330 ----------------------------
332 procedure Preprocess_Infile_Name is
340 -- Initialize the buffer with the name of the input file
342 Get_Name_String (Infile_Name);
345 while File_Name_Buffer'Length < Len loop
346 Double_File_Name_Buffer;
349 File_Name_Buffer (1 .. Len) := Name_Buffer (1 .. Len);
351 -- Look for possible symbols in the file name
354 while First < Len loop
356 -- A symbol starts with a dollar sign followed by a letter
358 if File_Name_Buffer (First) = '$' and then
359 Is_ASCII_Letter (File_Name_Buffer (First + 1))
363 -- Find the last letter of the symbol
365 while Last < Len and then
366 Is_ASCII_Letter (File_Name_Buffer (Last + 1))
371 -- Get the symbol name id
373 Name_Len := Last - First;
374 Name_Buffer (1 .. Name_Len) :=
375 File_Name_Buffer (First + 1 .. Last);
376 To_Lower (Name_Buffer (1 .. Name_Len));
379 -- And look for this symbol name in the symbol table
381 for Index in 1 .. Symbol_Table.Last (Mapping) loop
382 Data := Mapping.Table (Index);
384 if Data.Symbol = Symbol then
386 -- We found the symbol. If its value is not a string,
387 -- replace the symbol in the file name with the value of
390 if not Data.Is_A_String then
391 String_To_Name_Buffer (Data.Value);
394 Sym_Len : constant Positive := Last - First + 1;
395 Offset : constant Integer := Name_Len - Sym_Len;
396 New_Len : constant Natural := Len + Offset;
399 while New_Len > File_Name_Buffer'Length loop
400 Double_File_Name_Buffer;
403 File_Name_Buffer (Last + 1 + Offset .. New_Len) :=
404 File_Name_Buffer (Last + 1 .. Len);
406 Last := Last + Offset;
407 File_Name_Buffer (First .. Last) :=
408 Name_Buffer (1 .. Name_Len);
416 -- Skip over the symbol name or its value: we are not checking
417 -- for another symbol name in the value.
426 -- We now have the output file name in the buffer. Get the output
427 -- path and put it in Outfile_Name.
429 Get_Name_String (Output_Directory);
430 Add_Char_To_Name_Buffer (Directory_Separator);
431 Add_Str_To_Name_Buffer (File_Name_Buffer (1 .. Len));
432 Outfile_Name := Name_Find;
433 end Preprocess_Infile_Name;
435 --------------------------------------------
436 -- Process_Command_Line_Symbol_Definition --
437 --------------------------------------------
439 procedure Process_Command_Line_Symbol_Definition (S : String) is
444 -- Check the symbol definition and get the symbol and its value.
445 -- Fail if symbol definition is illegal.
447 Check_Command_Line_Symbol_Definition (S, Data);
449 Symbol := Index_Of (Data.Symbol);
451 -- If symbol does not alrady exist, create a new entry in the mapping
454 if Symbol = No_Symbol then
455 Symbol_Table.Increment_Last (Mapping);
456 Symbol := Symbol_Table.Last (Mapping);
459 Mapping.Table (Symbol) := Data;
460 end Process_Command_Line_Symbol_Definition;
466 procedure Process_Files is
468 procedure Process_One_File;
469 -- Process input file Infile_Name and put the result in file
472 procedure Recursive_Process (In_Dir : String; Out_Dir : String);
473 -- Process recursively files in In_Dir. Results go to Out_Dir
475 ----------------------
476 -- Process_One_File --
477 ----------------------
479 procedure Process_One_File is
480 Infile : Source_File_Index;
483 -- Create the output file (fails if this does not work)
486 Create (Text_Outfile, Out_File, Get_Name_String (Outfile_Name));
491 ("unable to create output file """,
492 Get_Name_String (Outfile_Name), """");
495 -- Load the input file
497 Infile := Sinput.C.Load_File (Get_Name_String (Infile_Name));
499 if Infile = No_Source_File then
500 Fail ("unable to find input file """,
501 Get_Name_String (Infile_Name), """");
504 -- Set Main_Source_File to the input file for the benefit of
507 Sinput.Main_Source_File := Infile;
509 Scanner.Initialize_Scanner (Infile);
511 -- Output the pragma Source_Reference if asked to
513 if Source_Ref_Pragma then
516 "pragma Source_Reference (1, """ &
517 Get_Name_String (Sinput.Full_File_Name (Infile)) & """);");
520 -- Preprocess the input file
524 -- In verbose mode, if there is no error, report it
526 if Opt.Verbose_Mode and then Err_Vars.Total_Errors_Detected = 0 then
527 Errutil.Finalize (Source_Type => "input");
530 -- If we had some errors, delete the output file, and report them
532 if Err_Vars.Total_Errors_Detected > 0 then
533 if Outfile /= Standard_Output then
534 Delete (Text_Outfile);
537 Errutil.Finalize (Source_Type => "input");
541 -- Otherwise, close the output file, and we are done
543 elsif Outfile /= Standard_Output then
544 Close (Text_Outfile);
546 end Process_One_File;
548 -----------------------
549 -- Recursive_Process --
550 -----------------------
552 procedure Recursive_Process (In_Dir : String; Out_Dir : String) is
554 Name : String (1 .. 255);
556 In_Dir_Name : Name_Id;
557 Out_Dir_Name : Name_Id;
559 procedure Set_Directory_Names;
560 -- Establish or reestablish the current input and output directories
562 -------------------------
563 -- Set_Directory_Names --
564 -------------------------
566 procedure Set_Directory_Names is
568 Input_Directory := In_Dir_Name;
569 Output_Directory := Out_Dir_Name;
570 end Set_Directory_Names;
572 -- Start of processing for Recursive_Process
575 -- Open the current input directory
578 Open (Dir_In, In_Dir);
581 when Directory_Error =>
582 Fail ("could not read directory " & In_Dir);
585 -- Set the new input and output directory names
587 Name_Len := In_Dir'Length;
588 Name_Buffer (1 .. Name_Len) := In_Dir;
589 In_Dir_Name := Name_Find;
590 Name_Len := Out_Dir'Length;
591 Name_Buffer (1 .. Name_Len) := Out_Dir;
592 Out_Dir_Name := Name_Find;
596 -- Traverse the input directory
598 Read (Dir_In, Name, Last);
601 if Name (1 .. Last) /= "." and then Name (1 .. Last) /= ".." then
603 Input : constant String :=
604 In_Dir & Directory_Separator & Name (1 .. Last);
605 Output : constant String :=
606 Out_Dir & Directory_Separator & Name (1 .. Last);
609 -- If input is an ordinary file, process it
611 if Is_Regular_File (Input) then
612 -- First get the output file name
615 Name_Buffer (1 .. Name_Len) := Name (1 .. Last);
616 Infile_Name := Name_Find;
617 Preprocess_Infile_Name;
619 -- Set the input file name and process the file
621 Name_Len := Input'Length;
622 Name_Buffer (1 .. Name_Len) := Input;
623 Infile_Name := Name_Find;
626 elsif Is_Directory (Input) then
627 -- Input is a directory. If the corresponding output
628 -- directory does not already exist, create it.
630 if not Is_Directory (Output) then
632 Make_Dir (Dir_Name => Output);
635 when Directory_Error =>
636 Fail ("could not create directory """,
641 -- And process this new input directory
643 Recursive_Process (Input, Output);
645 -- Reestablish the input and output directory names
646 -- that have been modified by the recursive call.
653 end Recursive_Process;
655 -- Start of processing for Process_Files
658 if Output_Directory = No_Name then
660 -- If the output is not a directory, fail if the input is
661 -- an existing directory, to avoid possible problems.
663 if Is_Directory (Get_Name_String (Infile_Name)) then
664 Fail ("input file """ & Get_Name_String (Infile_Name) &
665 """ is a directory");
668 -- Just process the single input file
672 elsif Input_Directory = No_Name then
674 -- Get the output file name from the input file name, and process
675 -- the single input file.
677 Preprocess_Infile_Name;
681 -- Recursively process files in the directory tree rooted at the
685 (In_Dir => Get_Name_String (Input_Directory),
686 Out_Dir => Get_Name_String (Output_Directory));
690 -------------------------
691 -- Put_Char_To_Outfile --
692 -------------------------
694 procedure Put_Char_To_Outfile (C : Character) is
696 Put (Outfile.all, C);
697 end Put_Char_To_Outfile;
699 -----------------------
700 -- Scan_Command_Line --
701 -----------------------
703 procedure Scan_Command_Line is
707 -- Parse the switches
711 Switch := GNAT.Command_Line.Getopt ("D: b c C r s u v");
719 Process_Command_Line_Symbol_Definition
720 (S => GNAT.Command_Line.Parameter);
723 Opt.Blank_Deleted_Lines := True;
726 Opt.Comment_Deleted_Lines := True;
729 Opt.Replace_In_Comments := True;
732 Source_Ref_Pragma := True;
735 Opt.List_Preprocessing_Symbols := True;
738 Opt.Undefined_Symbols_Are_False := True;
741 Opt.Verbose_Mode := True;
744 Fail ("Invalid Switch: -" & Switch);
748 when GNAT.Command_Line.Invalid_Switch =>
749 Write_Str ("Invalid Switch: -");
750 Write_Line (GNAT.Command_Line.Full_Switch);
756 -- Get the file names
760 S : constant String := GNAT.Command_Line.Get_Argument;
763 exit when S'Length = 0;
765 Name_Len := S'Length;
766 Name_Buffer (1 .. Name_Len) := S;
768 if Infile_Name = No_Name then
769 Infile_Name := Name_Find;
770 elsif Outfile_Name = No_Name then
771 Outfile_Name := Name_Find;
772 elsif Deffile_Name = No_Name then
773 Deffile_Name := Name_Find;
775 Fail ("too many arguments specifed");
779 end Scan_Command_Line;
788 Write_Line ("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
789 "infile outfile [deffile]");
791 Write_Line (" infile Name of the input file");
792 Write_Line (" outfile Name of the output file");
793 Write_Line (" deffile Name of the definition file");
795 Write_Line ("gnatprep switches:");
796 Write_Line (" -b Replace preprocessor lines by blank lines");
797 Write_Line (" -c Keep preprocessor lines as comments");
798 Write_Line (" -C Do symbol replacements within comments");
799 Write_Line (" -D Associate symbol with value");
800 Write_Line (" -r Generate Source_Reference pragma");
801 Write_Line (" -s Print a sorted list of symbol names and values");
802 Write_Line (" -u Treat undefined symbols as FALSE");
803 Write_Line (" -v Verbose mode");