OSDN Git Service

2007-04-20 Robert Dewar <dewar@adacore.com>
[pf3gnuchains/gcc-fork.git] / gcc / ada / gprep.adb
1 ------------------------------------------------------------------------------
2 --                                                                          --
3 --                         GNAT COMPILER COMPONENTS                         --
4 --                                                                          --
5 --                                G P R E P                                 --
6 --                                                                          --
7 --                                 B o d y                                  --
8 --                                                                          --
9 --          Copyright (C) 2002-2007, Free Software Foundation, Inc.         --
10 --                                                                          --
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.                                              --
21 --                                                                          --
22 -- GNAT was originally developed  by the GNAT team at  New York University. --
23 -- Extensive contributions were provided by Ada Core Technologies Inc.      --
24 --                                                                          --
25 ------------------------------------------------------------------------------
26
27 with Csets;
28 with Err_Vars; use Err_Vars;
29 with Errutil;
30 with Gnatvsn;  use Gnatvsn;
31 with Namet;    use Namet;
32 with Opt;
33 with Osint;    use Osint;
34 with Output;   use Output;
35 with Prep;     use Prep;
36 with Scng;
37 with Sinput.C;
38 with Snames;
39 with Stringt;  use Stringt;
40 with Types;    use Types;
41
42 with Ada.Text_IO;               use Ada.Text_IO;
43
44 with GNAT.Case_Util;            use GNAT.Case_Util;
45 with GNAT.Command_Line;
46 with GNAT.Directory_Operations; use GNAT.Directory_Operations;
47
48 with System.OS_Lib;             use System.OS_Lib;
49
50 package body GPrep is
51
52    Copyright_Displayed : Boolean := False;
53    --  Used to prevent multiple displays of the copyright notice
54
55    ------------------------
56    -- Argument Line Data --
57    ------------------------
58
59    Infile_Name  : Name_Id := No_Name;
60    Outfile_Name : Name_Id := No_Name;
61    Deffile_Name : Name_Id := No_Name;
62
63    Output_Directory : Name_Id := No_Name;
64    --  Used when the specified output is an existing directory
65
66    Input_Directory : Name_Id := No_Name;
67    --  Used when the specified input and output are existing directories
68
69    Source_Ref_Pragma : Boolean := False;
70    --  Record command line options (set if -r switch set)
71
72    Text_Outfile : aliased Ada.Text_IO.File_Type;
73    Outfile      : constant File_Access := Text_Outfile'Access;
74
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
79
80    -----------------
81    -- Subprograms --
82    -----------------
83
84    procedure Display_Copyright;
85    --  Display the copyright notice
86
87    procedure Obsolescent_Check (S : Source_Ptr);
88    --  Null procedure, needed by instantiation of Scng below
89
90    procedure Post_Scan;
91    --  Null procedure, needed by instantiation of Scng below
92
93    package Scanner is new Scng
94      (Post_Scan,
95       Errutil.Error_Msg,
96       Errutil.Error_Msg_S,
97       Errutil.Error_Msg_SC,
98       Errutil.Error_Msg_SP,
99       Obsolescent_Check,
100       Errutil.Style);
101    --  The scanner for the preprocessor
102
103    function Is_ASCII_Letter (C : Character) return Boolean;
104    --  True if C is in 'a' .. 'z' or in 'A' .. 'Z'
105
106    procedure Double_File_Name_Buffer;
107    --  Double the size of the file name buffer
108
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.
112
113    procedure Process_Files;
114    --  Process the single input file or all the files in the directory tree
115    --  rooted at the input directory.
116
117    procedure Process_Command_Line_Symbol_Definition (S : String);
118    --  Process a -D switch on the command line
119
120    procedure Put_Char_To_Outfile (C : Character);
121    --  Output one character to the output file. Used to initialize the
122    --  preprocessor.
123
124    procedure New_EOL_To_Outfile;
125    --  Output a new line to the output file. Used to initialize the
126    --  preprocessor.
127
128    procedure Scan_Command_Line;
129    --  Scan the switches and the file names
130
131    procedure Usage;
132    --  Display the usage
133
134    -----------------------
135    -- Display_Copyright --
136    -----------------------
137
138    procedure Display_Copyright is
139    begin
140       if not Copyright_Displayed then
141          Write_Line ("GNAT Preprocessor " & Gnatvsn.Gnat_Version_String);
142          Write_Line ("Copyright 1996-" &
143                      Current_Year &
144                      ", Free Software Foundation, Inc.");
145          Copyright_Displayed := True;
146       end if;
147    end Display_Copyright;
148
149    -----------------------------
150    -- Double_File_Name_Buffer --
151    -----------------------------
152
153    procedure Double_File_Name_Buffer is
154       New_Buffer : constant String_Access :=
155                      new String (1 .. 2 * File_Name_Buffer'Length);
156    begin
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;
161
162    --------------
163    -- Gnatprep --
164    --------------
165
166    procedure Gnatprep is
167    begin
168       --  Do some initializations (order is important here!)
169
170       Csets.Initialize;
171       Namet.Initialize;
172       Snames.Initialize;
173       Stringt.Initialize;
174
175       --  Initialize the preprocessor
176
177       Prep.Initialize
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);
183
184       --  Set the scanner characteristics for the preprocessor
185
186       Scanner.Set_Special_Character ('#');
187       Scanner.Set_Special_Character ('$');
188       Scanner.Set_End_Of_Line_As_Token (True);
189
190       --  Initialize the mapping table of symbols to values
191
192       Prep.Symbol_Table.Init (Prep.Mapping);
193
194       --  Parse the switches and arguments
195
196       Scan_Command_Line;
197
198       if Opt.Verbose_Mode then
199          Display_Copyright;
200       end if;
201
202       --  Test we had all the arguments needed
203
204       if Infile_Name = No_Name then
205
206          --  No input file specified, just output the usage and exit
207
208          Usage;
209          return;
210
211       elsif Outfile_Name = No_Name then
212
213          --  No output file specified, just output the usage and exit
214
215          Usage;
216          return;
217       end if;
218
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
221       --  blank lines.
222
223       if Source_Ref_Pragma and (not Opt.Comment_Deleted_Lines) then
224          Opt.Blank_Deleted_Lines := True;
225       end if;
226
227       --  If we have a definition file, parse it
228
229       if Deffile_Name /= No_Name then
230          declare
231             Deffile : Source_File_Index;
232
233          begin
234             Errutil.Initialize;
235             Deffile := Sinput.C.Load_File (Get_Name_String (Deffile_Name));
236
237             --  Set Main_Source_File to the definition file for the benefit of
238             --  Errutil.Finalize.
239
240             Sinput.Main_Source_File := Deffile;
241
242             if Deffile = No_Source_File then
243                Fail ("unable to find definition file """,
244                      Get_Name_String (Deffile_Name),
245                      """");
246             end if;
247
248             Scanner.Initialize_Scanner (Deffile);
249
250             Prep.Parse_Def_File;
251          end;
252       end if;
253
254       --  If there are errors in the definition file, output them and exit
255
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), """");
260       end if;
261
262       --  If -s switch was specified, print a sorted list of symbol names and
263       --  values, if any.
264
265       if Opt.List_Preprocessing_Symbols then
266          Prep.List_Symbols (Foreword => "");
267       end if;
268
269       Output_Directory := No_Name;
270       Input_Directory  := No_Name;
271
272       --  Check if the specified output is an existing directory
273
274       if Is_Directory (Get_Name_String (Outfile_Name)) then
275          Output_Directory := Outfile_Name;
276
277          --  As the output is an existing directory, check if the input too
278          --  is a directory.
279
280          if Is_Directory (Get_Name_String (Infile_Name)) then
281             Input_Directory := Infile_Name;
282          end if;
283       end if;
284
285       --  And process the single input or the files in the directory tree
286       --  rooted at the input directory.
287
288       Process_Files;
289    end Gnatprep;
290
291    ---------------------
292    -- Is_ASCII_Letter --
293    ---------------------
294
295    function Is_ASCII_Letter (C : Character) return Boolean is
296    begin
297       return C in 'A' .. 'Z' or else C in 'a' .. 'z';
298    end Is_ASCII_Letter;
299
300    ------------------------
301    -- New_EOL_To_Outfile --
302    ------------------------
303
304    procedure New_EOL_To_Outfile is
305    begin
306       New_Line (Outfile.all);
307    end New_EOL_To_Outfile;
308
309    -----------------------
310    -- Obsolescent_Check --
311    -----------------------
312
313    procedure Obsolescent_Check (S : Source_Ptr) is
314       pragma Warnings (Off, S);
315    begin
316       null;
317    end Obsolescent_Check;
318
319    ---------------
320    -- Post_Scan --
321    ---------------
322
323    procedure Post_Scan is
324    begin
325       null;
326    end Post_Scan;
327
328    ----------------------------
329    -- Preprocess_Infile_Name --
330    ----------------------------
331
332    procedure Preprocess_Infile_Name is
333       Len    : Natural;
334       First  : Positive;
335       Last   : Natural;
336       Symbol : Name_Id;
337       Data   : Symbol_Data;
338
339    begin
340       --  Initialize the buffer with the name of the input file
341
342       Get_Name_String (Infile_Name);
343       Len := Name_Len;
344
345       while File_Name_Buffer'Length < Len loop
346          Double_File_Name_Buffer;
347       end loop;
348
349       File_Name_Buffer (1 .. Len) := Name_Buffer (1 .. Len);
350
351       --  Look for possible symbols in the file name
352
353       First := 1;
354       while First < Len loop
355
356          --  A symbol starts with a dollar sign followed by a letter
357
358          if File_Name_Buffer (First) = '$' and then
359            Is_ASCII_Letter (File_Name_Buffer (First + 1))
360          then
361             Last := First + 1;
362
363             --  Find the last letter of the symbol
364
365             while Last < Len and then
366                Is_ASCII_Letter (File_Name_Buffer (Last + 1))
367             loop
368                Last := Last + 1;
369             end loop;
370
371             --  Get the symbol name id
372
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));
377             Symbol := Name_Find;
378
379             --  And look for this symbol name in the symbol table
380
381             for Index in 1 .. Symbol_Table.Last (Mapping) loop
382                Data := Mapping.Table (Index);
383
384                if Data.Symbol = Symbol then
385
386                   --  We found the symbol. If its value is not a string,
387                   --  replace the symbol in the file name with the value of
388                   --  the symbol.
389
390                   if not Data.Is_A_String then
391                      String_To_Name_Buffer (Data.Value);
392
393                      declare
394                         Sym_Len : constant Positive := Last - First + 1;
395                         Offset  : constant Integer := Name_Len - Sym_Len;
396                         New_Len : constant Natural := Len + Offset;
397
398                      begin
399                         while New_Len > File_Name_Buffer'Length loop
400                            Double_File_Name_Buffer;
401                         end loop;
402
403                         File_Name_Buffer (Last + 1 + Offset .. New_Len) :=
404                           File_Name_Buffer (Last + 1 .. Len);
405                         Len := New_Len;
406                         Last := Last + Offset;
407                         File_Name_Buffer (First .. Last) :=
408                           Name_Buffer (1 .. Name_Len);
409                      end;
410                   end if;
411
412                   exit;
413                end if;
414             end loop;
415
416             --  Skip over the symbol name or its value: we are not checking
417             --  for another symbol name in the value.
418
419             First := Last + 1;
420
421          else
422             First := First + 1;
423          end if;
424       end loop;
425
426       --  We now have the output file name in the buffer. Get the output
427       --  path and put it in Outfile_Name.
428
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;
434
435    --------------------------------------------
436    -- Process_Command_Line_Symbol_Definition --
437    --------------------------------------------
438
439    procedure Process_Command_Line_Symbol_Definition (S : String) is
440       Data   : Symbol_Data;
441       Symbol : Symbol_Id;
442
443    begin
444       --  Check the symbol definition and get the symbol and its value.
445       --  Fail if symbol definition is illegal.
446
447       Check_Command_Line_Symbol_Definition (S, Data);
448
449       Symbol := Index_Of (Data.Symbol);
450
451       --  If symbol does not alrady exist, create a new entry in the mapping
452       --  table.
453
454       if Symbol = No_Symbol then
455          Symbol_Table.Increment_Last (Mapping);
456          Symbol := Symbol_Table.Last (Mapping);
457       end if;
458
459       Mapping.Table (Symbol) := Data;
460    end Process_Command_Line_Symbol_Definition;
461
462    -------------------
463    -- Process_Files --
464    -------------------
465
466    procedure Process_Files is
467
468       procedure Process_One_File;
469       --  Process input file Infile_Name and put the result in file
470       --  Outfile_Name.
471
472       procedure Recursive_Process (In_Dir : String; Out_Dir : String);
473       --  Process recursively files in In_Dir. Results go to Out_Dir
474
475       ----------------------
476       -- Process_One_File --
477       ----------------------
478
479       procedure Process_One_File is
480          Infile : Source_File_Index;
481
482       begin
483          --  Create the output file (fails if this does not work)
484
485          begin
486             Create (Text_Outfile, Out_File, Get_Name_String (Outfile_Name));
487
488          exception
489             when others =>
490                Fail
491                  ("unable to create output file """,
492                   Get_Name_String (Outfile_Name), """");
493          end;
494
495          --  Load the input file
496
497          Infile := Sinput.C.Load_File (Get_Name_String (Infile_Name));
498
499          if Infile = No_Source_File then
500             Fail ("unable to find input file """,
501                   Get_Name_String (Infile_Name), """");
502          end if;
503
504          --  Set Main_Source_File to the input file for the benefit of
505          --  Errutil.Finalize.
506
507          Sinput.Main_Source_File := Infile;
508
509          Scanner.Initialize_Scanner (Infile);
510
511          --  Output the pragma Source_Reference if asked to
512
513          if Source_Ref_Pragma then
514             Put_Line
515               (Outfile.all,
516                "pragma Source_Reference (1, """ &
517                  Get_Name_String (Sinput.Full_File_Name (Infile)) & """);");
518          end if;
519
520          --  Preprocess the input file
521
522          Prep.Preprocess;
523
524          --  In verbose mode, if there is no error, report it
525
526          if Opt.Verbose_Mode and then Err_Vars.Total_Errors_Detected = 0 then
527             Errutil.Finalize (Source_Type => "input");
528          end if;
529
530          --  If we had some errors, delete the output file, and report them
531
532          if Err_Vars.Total_Errors_Detected > 0 then
533             if Outfile /= Standard_Output then
534                Delete (Text_Outfile);
535             end if;
536
537             Errutil.Finalize (Source_Type => "input");
538
539             OS_Exit (0);
540
541          --  Otherwise, close the output file, and we are done
542
543          elsif Outfile /= Standard_Output then
544             Close (Text_Outfile);
545          end if;
546       end Process_One_File;
547
548       -----------------------
549       -- Recursive_Process --
550       -----------------------
551
552       procedure Recursive_Process (In_Dir : String; Out_Dir : String) is
553          Dir_In : Dir_Type;
554          Name : String (1 .. 255);
555          Last : Natural;
556          In_Dir_Name  : Name_Id;
557          Out_Dir_Name : Name_Id;
558
559          procedure Set_Directory_Names;
560          --  Establish or reestablish the current input and output directories
561
562          -------------------------
563          -- Set_Directory_Names --
564          -------------------------
565
566          procedure Set_Directory_Names is
567          begin
568             Input_Directory := In_Dir_Name;
569             Output_Directory := Out_Dir_Name;
570          end Set_Directory_Names;
571
572       --  Start of processing for Recursive_Process
573
574       begin
575          --  Open the current input directory
576
577          begin
578             Open (Dir_In, In_Dir);
579
580          exception
581             when Directory_Error =>
582                Fail ("could not read directory " & In_Dir);
583          end;
584
585          --  Set the new input and output directory names
586
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;
593
594          Set_Directory_Names;
595
596          --  Traverse the input directory
597          loop
598             Read (Dir_In, Name, Last);
599             exit when Last = 0;
600
601             if Name (1 .. Last) /= "." and then Name (1 .. Last) /= ".." then
602                declare
603                   Input : constant String :=
604                             In_Dir & Directory_Separator & Name (1 .. Last);
605                   Output : constant String :=
606                              Out_Dir & Directory_Separator & Name (1 .. Last);
607
608                begin
609                   --  If input is an ordinary file, process it
610
611                   if Is_Regular_File (Input) then
612                      --  First get the output file name
613
614                      Name_Len := Last;
615                      Name_Buffer (1 .. Name_Len) := Name (1 .. Last);
616                      Infile_Name := Name_Find;
617                      Preprocess_Infile_Name;
618
619                      --  Set the input file name and process the file
620
621                      Name_Len := Input'Length;
622                      Name_Buffer (1 .. Name_Len) := Input;
623                      Infile_Name := Name_Find;
624                      Process_One_File;
625
626                   elsif Is_Directory (Input) then
627                      --  Input is a directory. If the corresponding output
628                      --  directory does not already exist, create it.
629
630                      if not Is_Directory (Output) then
631                         begin
632                            Make_Dir (Dir_Name => Output);
633
634                         exception
635                            when Directory_Error =>
636                               Fail ("could not create directory """,
637                                     Output, """");
638                         end;
639                      end if;
640
641                      --  And process this new input directory
642
643                      Recursive_Process (Input, Output);
644
645                      --  Reestablish the input and output directory names
646                      --  that have been modified by the recursive call.
647
648                      Set_Directory_Names;
649                   end if;
650                end;
651             end if;
652          end loop;
653       end Recursive_Process;
654
655    --  Start of processing for Process_Files
656
657    begin
658       if Output_Directory = No_Name then
659
660          --  If the output is not a directory, fail if the input is
661          --  an existing directory, to avoid possible problems.
662
663          if Is_Directory (Get_Name_String (Infile_Name)) then
664             Fail ("input file """ & Get_Name_String (Infile_Name) &
665                   """ is a directory");
666          end if;
667
668          --  Just process the single input file
669
670          Process_One_File;
671
672       elsif Input_Directory = No_Name then
673
674          --  Get the output file name from the input file name, and process
675          --  the single input file.
676
677          Preprocess_Infile_Name;
678          Process_One_File;
679
680       else
681          --  Recursively process files in the directory tree rooted at the
682          --  input directory.
683
684          Recursive_Process
685            (In_Dir => Get_Name_String (Input_Directory),
686             Out_Dir => Get_Name_String (Output_Directory));
687       end if;
688    end Process_Files;
689
690    -------------------------
691    -- Put_Char_To_Outfile --
692    -------------------------
693
694    procedure Put_Char_To_Outfile (C : Character) is
695    begin
696       Put (Outfile.all, C);
697    end Put_Char_To_Outfile;
698
699    -----------------------
700    -- Scan_Command_Line --
701    -----------------------
702
703    procedure Scan_Command_Line is
704       Switch : Character;
705
706    begin
707       --  Parse the switches
708
709       loop
710          begin
711             Switch := GNAT.Command_Line.Getopt ("D: b c C r s u v");
712
713             case Switch is
714
715                when ASCII.NUL =>
716                   exit;
717
718                when 'D' =>
719                   Process_Command_Line_Symbol_Definition
720                     (S => GNAT.Command_Line.Parameter);
721
722                when 'b' =>
723                   Opt.Blank_Deleted_Lines := True;
724
725                when 'c' =>
726                   Opt.Comment_Deleted_Lines := True;
727
728                when 'C' =>
729                   Opt.Replace_In_Comments := True;
730
731                when 'r' =>
732                   Source_Ref_Pragma := True;
733
734                when 's' =>
735                   Opt.List_Preprocessing_Symbols := True;
736
737                when 'u' =>
738                   Opt.Undefined_Symbols_Are_False := True;
739
740                when 'v' =>
741                   Opt.Verbose_Mode := True;
742
743                when others =>
744                   Fail ("Invalid Switch: -" & Switch);
745             end case;
746
747          exception
748             when GNAT.Command_Line.Invalid_Switch =>
749                Write_Str ("Invalid Switch: -");
750                Write_Line (GNAT.Command_Line.Full_Switch);
751                Usage;
752                OS_Exit (1);
753          end;
754       end loop;
755
756       --  Get the file names
757
758       loop
759          declare
760             S : constant String := GNAT.Command_Line.Get_Argument;
761
762          begin
763             exit when S'Length = 0;
764
765             Name_Len := S'Length;
766             Name_Buffer (1 .. Name_Len) := S;
767
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;
774             else
775                Fail ("too many arguments specifed");
776             end if;
777          end;
778       end loop;
779    end Scan_Command_Line;
780
781    -----------
782    -- Usage --
783    -----------
784
785    procedure Usage is
786    begin
787       Display_Copyright;
788       Write_Line ("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
789                     "infile outfile [deffile]");
790       Write_Eol;
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");
794       Write_Eol;
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");
804       Write_Eol;
805    end Usage;
806
807 end GPrep;