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