1------------------------------------------------------------------------------
2--                                                                          --
3--                         GNAT COMPILER COMPONENTS                         --
4--                                                                          --
5--                                G P R E P                                 --
6--                                                                          --
7--                                 B o d y                                  --
8--                                                                          --
9--          Copyright (C) 2002-2013, 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 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.          --
20--                                                                          --
21-- GNAT was originally developed  by the GNAT team at  New York University. --
22-- Extensive contributions were provided by Ada Core Technologies Inc.      --
23--                                                                          --
24------------------------------------------------------------------------------
25
26with Atree;    use Atree;
27with Csets;
28with Errutil;
29with Namet;    use Namet;
30with Opt;
31with Osint;    use Osint;
32with Output;   use Output;
33with Prep;     use Prep;
34with Scng;
35with Sinput.C;
36with Snames;
37with Stringt;  use Stringt;
38with Switch;   use Switch;
39with Types;    use Types;
40
41with Ada.Text_IO;     use Ada.Text_IO;
42
43with GNAT.Case_Util;            use GNAT.Case_Util;
44with GNAT.Command_Line;
45with GNAT.Directory_Operations; use GNAT.Directory_Operations;
46
47with System.OS_Lib; use System.OS_Lib;
48
49package body GPrep is
50
51   Copyright_Displayed : Boolean := False;
52   --  Used to prevent multiple displays of the copyright notice
53
54   ------------------------
55   -- Argument Line Data --
56   ------------------------
57
58   Unix_Line_Terminators : Boolean := False;
59   --  Set to True with option -T
60
61   type String_Array is array (Boolean) of String_Access;
62   Yes_No : constant String_Array :=
63     (False => new String'("YES"),
64      True  => new String'("NO"));
65
66   Infile_Name  : Name_Id := No_Name;
67   Outfile_Name : Name_Id := No_Name;
68   Deffile_Name : Name_Id := No_Name;
69
70   Output_Directory : Name_Id := No_Name;
71   --  Used when the specified output is an existing directory
72
73   Input_Directory : Name_Id := No_Name;
74   --  Used when the specified input and output are existing directories
75
76   Source_Ref_Pragma : Boolean := False;
77   --  Record command line options (set if -r switch set)
78
79   Text_Outfile : aliased Ada.Text_IO.File_Type;
80   Outfile      : constant File_Access := Text_Outfile'Access;
81
82   File_Name_Buffer_Initial_Size : constant := 50;
83   File_Name_Buffer : String_Access :=
84                        new String (1 .. File_Name_Buffer_Initial_Size);
85   --  A buffer to build output file names from input file names
86
87   -----------------
88   -- Subprograms --
89   -----------------
90
91   procedure Display_Copyright;
92   --  Display the copyright notice
93
94   procedure Post_Scan;
95   --  Null procedure, needed by instantiation of Scng below
96
97   package Scanner is new Scng
98     (Post_Scan,
99      Errutil.Error_Msg,
100      Errutil.Error_Msg_S,
101      Errutil.Error_Msg_SC,
102      Errutil.Error_Msg_SP,
103      Errutil.Style);
104   --  The scanner for the preprocessor
105
106   function Is_ASCII_Letter (C : Character) return Boolean;
107   --  True if C is in 'a' .. 'z' or in 'A' .. 'Z'
108
109   procedure Double_File_Name_Buffer;
110   --  Double the size of the file name buffer
111
112   procedure Preprocess_Infile_Name;
113   --  When the specified output is a directory, preprocess the infile name
114   --  for symbol substitution, to get the output file name.
115
116   procedure Process_Files;
117   --  Process the single input file or all the files in the directory tree
118   --  rooted at the input directory.
119
120   procedure Process_Command_Line_Symbol_Definition (S : String);
121   --  Process a -D switch on the command line
122
123   procedure Put_Char_To_Outfile (C : Character);
124   --  Output one character to the output file. Used to initialize the
125   --  preprocessor.
126
127   procedure New_EOL_To_Outfile;
128   --  Output a new line to the output file. Used to initialize the
129   --  preprocessor.
130
131   procedure Scan_Command_Line;
132   --  Scan the switches and the file names
133
134   procedure Usage;
135   --  Display the usage
136
137   -----------------------
138   -- Display_Copyright --
139   -----------------------
140
141   procedure Display_Copyright is
142   begin
143      if not Copyright_Displayed then
144         Display_Version ("GNAT Preprocessor", "1996");
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      Snames.Initialize;
172      Stringt.Initialize;
173      Prep.Initialize;
174
175      --  Initialize the preprocessor
176
177      Prep.Setup_Hooks
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               & """");
261      end if;
262
263      --  If -s switch was specified, print a sorted list of symbol names and
264      --  values, if any.
265
266      if Opt.List_Preprocessing_Symbols then
267         Prep.List_Symbols (Foreword => "");
268      end if;
269
270      Output_Directory := No_Name;
271      Input_Directory  := No_Name;
272
273      --  Check if the specified output is an existing directory
274
275      if Is_Directory (Get_Name_String (Outfile_Name)) then
276         Output_Directory := Outfile_Name;
277
278         --  As the output is an existing directory, check if the input too
279         --  is a directory.
280
281         if Is_Directory (Get_Name_String (Infile_Name)) then
282            Input_Directory := Infile_Name;
283         end if;
284      end if;
285
286      --  And process the single input or the files in the directory tree
287      --  rooted at the input directory.
288
289      Process_Files;
290   end Gnatprep;
291
292   ---------------------
293   -- Is_ASCII_Letter --
294   ---------------------
295
296   function Is_ASCII_Letter (C : Character) return Boolean is
297   begin
298      return C in 'A' .. 'Z' or else C in 'a' .. 'z';
299   end Is_ASCII_Letter;
300
301   ------------------------
302   -- New_EOL_To_Outfile --
303   ------------------------
304
305   procedure New_EOL_To_Outfile is
306   begin
307      New_Line (Outfile.all);
308   end New_EOL_To_Outfile;
309
310   ---------------
311   -- Post_Scan --
312   ---------------
313
314   procedure Post_Scan is
315   begin
316      null;
317   end Post_Scan;
318
319   ----------------------------
320   -- Preprocess_Infile_Name --
321   ----------------------------
322
323   procedure Preprocess_Infile_Name is
324      Len    : Natural;
325      First  : Positive;
326      Last   : Natural;
327      Symbol : Name_Id;
328      Data   : Symbol_Data;
329
330   begin
331      --  Initialize the buffer with the name of the input file
332
333      Get_Name_String (Infile_Name);
334      Len := Name_Len;
335
336      while File_Name_Buffer'Length < Len loop
337         Double_File_Name_Buffer;
338      end loop;
339
340      File_Name_Buffer (1 .. Len) := Name_Buffer (1 .. Len);
341
342      --  Look for possible symbols in the file name
343
344      First := 1;
345      while First < Len loop
346
347         --  A symbol starts with a dollar sign followed by a letter
348
349         if File_Name_Buffer (First) = '$' and then
350           Is_ASCII_Letter (File_Name_Buffer (First + 1))
351         then
352            Last := First + 1;
353
354            --  Find the last letter of the symbol
355
356            while Last < Len and then
357               Is_ASCII_Letter (File_Name_Buffer (Last + 1))
358            loop
359               Last := Last + 1;
360            end loop;
361
362            --  Get the symbol name id
363
364            Name_Len := Last - First;
365            Name_Buffer (1 .. Name_Len) :=
366              File_Name_Buffer (First + 1 .. Last);
367            To_Lower (Name_Buffer (1 .. Name_Len));
368            Symbol := Name_Find;
369
370            --  And look for this symbol name in the symbol table
371
372            for Index in 1 .. Symbol_Table.Last (Mapping) loop
373               Data := Mapping.Table (Index);
374
375               if Data.Symbol = Symbol then
376
377                  --  We found the symbol. If its value is not a string,
378                  --  replace the symbol in the file name with the value of
379                  --  the symbol.
380
381                  if not Data.Is_A_String then
382                     String_To_Name_Buffer (Data.Value);
383
384                     declare
385                        Sym_Len : constant Positive := Last - First + 1;
386                        Offset  : constant Integer := Name_Len - Sym_Len;
387                        New_Len : constant Natural := Len + Offset;
388
389                     begin
390                        while New_Len > File_Name_Buffer'Length loop
391                           Double_File_Name_Buffer;
392                        end loop;
393
394                        File_Name_Buffer (Last + 1 + Offset .. New_Len) :=
395                          File_Name_Buffer (Last + 1 .. Len);
396                        Len := New_Len;
397                        Last := Last + Offset;
398                        File_Name_Buffer (First .. Last) :=
399                          Name_Buffer (1 .. Name_Len);
400                     end;
401                  end if;
402
403                  exit;
404               end if;
405            end loop;
406
407            --  Skip over the symbol name or its value: we are not checking
408            --  for another symbol name in the value.
409
410            First := Last + 1;
411
412         else
413            First := First + 1;
414         end if;
415      end loop;
416
417      --  We now have the output file name in the buffer. Get the output
418      --  path and put it in Outfile_Name.
419
420      Get_Name_String (Output_Directory);
421      Add_Char_To_Name_Buffer (Directory_Separator);
422      Add_Str_To_Name_Buffer (File_Name_Buffer (1 .. Len));
423      Outfile_Name := Name_Find;
424   end Preprocess_Infile_Name;
425
426   --------------------------------------------
427   -- Process_Command_Line_Symbol_Definition --
428   --------------------------------------------
429
430   procedure Process_Command_Line_Symbol_Definition (S : String) is
431      Data   : Symbol_Data;
432      Symbol : Symbol_Id;
433
434   begin
435      --  Check the symbol definition and get the symbol and its value.
436      --  Fail if symbol definition is illegal.
437
438      Check_Command_Line_Symbol_Definition (S, Data);
439
440      Symbol := Index_Of (Data.Symbol);
441
442      --  If symbol does not already exist, create a new entry in the mapping
443      --  table.
444
445      if Symbol = No_Symbol then
446         Symbol_Table.Increment_Last (Mapping);
447         Symbol := Symbol_Table.Last (Mapping);
448      end if;
449
450      Mapping.Table (Symbol) := Data;
451   end Process_Command_Line_Symbol_Definition;
452
453   -------------------
454   -- Process_Files --
455   -------------------
456
457   procedure Process_Files is
458
459      procedure Process_One_File;
460      --  Process input file Infile_Name and put the result in file
461      --  Outfile_Name.
462
463      procedure Recursive_Process (In_Dir : String; Out_Dir : String);
464      --  Process recursively files in In_Dir. Results go to Out_Dir
465
466      ----------------------
467      -- Process_One_File --
468      ----------------------
469
470      procedure Process_One_File is
471         Infile : Source_File_Index;
472
473         Modified : Boolean;
474         pragma Warnings (Off, Modified);
475
476      begin
477         --  Create the output file (fails if this does not work)
478
479         begin
480            Create
481              (File => Text_Outfile,
482               Mode => Out_File,
483               Name => Get_Name_String (Outfile_Name),
484               Form => "Text_Translation=" &
485                       Yes_No (Unix_Line_Terminators).all);
486
487         exception
488            when others =>
489               Fail
490                 ("unable to create output file """
491                  & Get_Name_String (Outfile_Name)
492                  & """");
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                  & """");
503         end if;
504
505         --  Set Main_Source_File to the input file for the benefit of
506         --  Errutil.Finalize.
507
508         Sinput.Main_Source_File := Infile;
509
510         Scanner.Initialize_Scanner (Infile);
511
512         --  Output the pragma Source_Reference if asked to
513
514         if Source_Ref_Pragma then
515            Put_Line
516              (Outfile.all,
517               "pragma Source_Reference (1, """ &
518                 Get_Name_String (Sinput.Full_File_Name (Infile)) & """);");
519         end if;
520
521         --  Preprocess the input file
522
523         Prep.Preprocess (Modified);
524
525         --  In verbose mode, if there is no error, report it
526
527         if Opt.Verbose_Mode and then Total_Errors_Detected = 0 then
528            Errutil.Finalize (Source_Type => "input");
529         end if;
530
531         --  If we had some errors, delete the output file, and report them
532
533         if Total_Errors_Detected > 0 then
534            if Outfile /= Standard_Output then
535               Delete (Text_Outfile);
536            end if;
537
538            Errutil.Finalize (Source_Type => "input");
539
540            OS_Exit (0);
541
542         --  Otherwise, close the output file, and we are done
543
544         elsif Outfile /= Standard_Output then
545            Close (Text_Outfile);
546         end if;
547      end Process_One_File;
548
549      -----------------------
550      -- Recursive_Process --
551      -----------------------
552
553      procedure Recursive_Process (In_Dir : String; Out_Dir : String) is
554         Dir_In : Dir_Type;
555         Name : String (1 .. 255);
556         Last : Natural;
557         In_Dir_Name  : Name_Id;
558         Out_Dir_Name : Name_Id;
559
560         procedure Set_Directory_Names;
561         --  Establish or reestablish the current input and output directories
562
563         -------------------------
564         -- Set_Directory_Names --
565         -------------------------
566
567         procedure Set_Directory_Names is
568         begin
569            Input_Directory := In_Dir_Name;
570            Output_Directory := Out_Dir_Name;
571         end Set_Directory_Names;
572
573      --  Start of processing for Recursive_Process
574
575      begin
576         --  Open the current input directory
577
578         begin
579            Open (Dir_In, In_Dir);
580
581         exception
582            when Directory_Error =>
583               Fail ("could not read directory " & In_Dir);
584         end;
585
586         --  Set the new input and output directory names
587
588         Name_Len := In_Dir'Length;
589         Name_Buffer (1 .. Name_Len) := In_Dir;
590         In_Dir_Name := Name_Find;
591         Name_Len := Out_Dir'Length;
592         Name_Buffer (1 .. Name_Len) := Out_Dir;
593         Out_Dir_Name := Name_Find;
594
595         Set_Directory_Names;
596
597         --  Traverse the input directory
598         loop
599            Read (Dir_In, Name, Last);
600            exit when Last = 0;
601
602            if Name (1 .. Last) /= "." and then Name (1 .. Last) /= ".." then
603               declare
604                  Input : constant String :=
605                            In_Dir & Directory_Separator & Name (1 .. Last);
606                  Output : constant String :=
607                             Out_Dir & Directory_Separator & Name (1 .. Last);
608
609               begin
610                  --  If input is an ordinary file, process it
611
612                  if Is_Regular_File (Input) then
613                     --  First get the output file name
614
615                     Name_Len := Last;
616                     Name_Buffer (1 .. Name_Len) := Name (1 .. Last);
617                     Infile_Name := Name_Find;
618                     Preprocess_Infile_Name;
619
620                     --  Set the input file name and process the file
621
622                     Name_Len := Input'Length;
623                     Name_Buffer (1 .. Name_Len) := Input;
624                     Infile_Name := Name_Find;
625                     Process_One_File;
626
627                  elsif Is_Directory (Input) then
628                     --  Input is a directory. If the corresponding output
629                     --  directory does not already exist, create it.
630
631                     if not Is_Directory (Output) then
632                        begin
633                           Make_Dir (Dir_Name => Output);
634
635                        exception
636                           when Directory_Error =>
637                              Fail ("could not create directory """
638                                    & Output
639                                    & """");
640                        end;
641                     end if;
642
643                     --  And process this new input directory
644
645                     Recursive_Process (Input, Output);
646
647                     --  Reestablish the input and output directory names
648                     --  that have been modified by the recursive call.
649
650                     Set_Directory_Names;
651                  end if;
652               end;
653            end if;
654         end loop;
655      end Recursive_Process;
656
657   --  Start of processing for Process_Files
658
659   begin
660      if Output_Directory = No_Name then
661
662         --  If the output is not a directory, fail if the input is
663         --  an existing directory, to avoid possible problems.
664
665         if Is_Directory (Get_Name_String (Infile_Name)) then
666            Fail ("input file """ & Get_Name_String (Infile_Name) &
667                  """ is a directory");
668         end if;
669
670         --  Just process the single input file
671
672         Process_One_File;
673
674      elsif Input_Directory = No_Name then
675
676         --  Get the output file name from the input file name, and process
677         --  the single input file.
678
679         Preprocess_Infile_Name;
680         Process_One_File;
681
682      else
683         --  Recursively process files in the directory tree rooted at the
684         --  input directory.
685
686         Recursive_Process
687           (In_Dir => Get_Name_String (Input_Directory),
688            Out_Dir => Get_Name_String (Output_Directory));
689      end if;
690   end Process_Files;
691
692   -------------------------
693   -- Put_Char_To_Outfile --
694   -------------------------
695
696   procedure Put_Char_To_Outfile (C : Character) is
697   begin
698      Put (Outfile.all, C);
699   end Put_Char_To_Outfile;
700
701   -----------------------
702   -- Scan_Command_Line --
703   -----------------------
704
705   procedure Scan_Command_Line is
706      Switch : Character;
707
708      procedure Check_Version_And_Help is new Check_Version_And_Help_G (Usage);
709
710      --  Start of processing for Scan_Command_Line
711
712   begin
713      --  First check for --version or --help
714
715      Check_Version_And_Help ("GNATPREP", "1996");
716
717      --  Now scan the other switches
718
719      GNAT.Command_Line.Initialize_Option_Scan;
720
721      loop
722         begin
723            Switch := GNAT.Command_Line.Getopt ("D: a b c C r s T u v");
724
725            case Switch is
726
727               when ASCII.NUL =>
728                  exit;
729
730               when 'D' =>
731                  Process_Command_Line_Symbol_Definition
732                    (S => GNAT.Command_Line.Parameter);
733
734               when 'a' =>
735                  Opt.No_Deletion := True;
736                  Opt.Undefined_Symbols_Are_False := True;
737
738               when 'b' =>
739                  Opt.Blank_Deleted_Lines := True;
740
741               when 'c' =>
742                  Opt.Comment_Deleted_Lines := True;
743
744               when 'C' =>
745                  Opt.Replace_In_Comments := True;
746
747               when 'r' =>
748                  Source_Ref_Pragma := True;
749
750               when 's' =>
751                  Opt.List_Preprocessing_Symbols := True;
752
753               when 'T' =>
754                  Unix_Line_Terminators := True;
755
756               when 'u' =>
757                  Opt.Undefined_Symbols_Are_False := True;
758
759               when 'v' =>
760                  Opt.Verbose_Mode := True;
761
762               when others =>
763                  Fail ("Invalid Switch: -" & Switch);
764            end case;
765
766         exception
767            when GNAT.Command_Line.Invalid_Switch =>
768               Write_Str ("Invalid Switch: -");
769               Write_Line (GNAT.Command_Line.Full_Switch);
770               Usage;
771               OS_Exit (1);
772         end;
773      end loop;
774
775      --  Get the file names
776
777      loop
778         declare
779            S : constant String := GNAT.Command_Line.Get_Argument;
780
781         begin
782            exit when S'Length = 0;
783
784            Name_Len := S'Length;
785            Name_Buffer (1 .. Name_Len) := S;
786
787            if Infile_Name = No_Name then
788               Infile_Name := Name_Find;
789            elsif Outfile_Name = No_Name then
790               Outfile_Name := Name_Find;
791            elsif Deffile_Name = No_Name then
792               Deffile_Name := Name_Find;
793            else
794               Fail ("too many arguments specified");
795            end if;
796         end;
797      end loop;
798   end Scan_Command_Line;
799
800   -----------
801   -- Usage --
802   -----------
803
804   procedure Usage is
805   begin
806      Display_Copyright;
807      Write_Line ("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
808                    "infile outfile [deffile]");
809      Write_Eol;
810      Write_Line ("  infile     Name of the input file");
811      Write_Line ("  outfile    Name of the output file");
812      Write_Line ("  deffile    Name of the definition file");
813      Write_Eol;
814      Write_Line ("gnatprep switches:");
815      Display_Usage_Version_And_Help;
816      Write_Line ("   -b  Replace preprocessor lines by blank lines");
817      Write_Line ("   -c  Keep preprocessor lines as comments");
818      Write_Line ("   -C  Do symbol replacements within comments");
819      Write_Line ("   -D  Associate symbol with value");
820      Write_Line ("   -r  Generate Source_Reference pragma");
821      Write_Line ("   -s  Print a sorted list of symbol names and values");
822      Write_Line ("   -T  Use LF as line terminators");
823      Write_Line ("   -u  Treat undefined symbols as FALSE");
824      Write_Line ("   -v  Verbose mode");
825      Write_Eol;
826   end Usage;
827
828end GPrep;
829