[multiple changes]
[gcc.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-2014, 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
26 with Atree; use Atree;
27 with Csets;
28 with Errutil;
29 with Namet; use Namet;
30 with Opt;
31 with Osint; use Osint;
32 with Output; use Output;
33 with Prep; use Prep;
34 with Scng;
35 with Sinput.C;
36 with Snames;
37 with Stringt; use Stringt;
38 with Switch; use Switch;
39 with Types; use Types;
40
41 with Ada.Command_Line; use Ada.Command_Line;
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 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 end if;
253
254 Scanner.Initialize_Scanner (Deffile);
255
256 Prep.Parse_Def_File;
257 end;
258 end if;
259
260 -- If there are errors in the definition file, output them and exit
261
262 if Total_Errors_Detected > 0 then
263 Errutil.Finalize (Source_Type => "definition");
264 Fail ("errors in definition file """
265 & Get_Name_String (Deffile_Name)
266 & """");
267 end if;
268
269 -- If -s switch was specified, print a sorted list of symbol names and
270 -- values, if any.
271
272 if Opt.List_Preprocessing_Symbols then
273 Prep.List_Symbols (Foreword => "");
274 end if;
275
276 Output_Directory := No_Name;
277 Input_Directory := No_Name;
278
279 -- Check if the specified output is an existing directory
280
281 if Is_Directory (Get_Name_String (Outfile_Name)) then
282 Output_Directory := Outfile_Name;
283
284 -- As the output is an existing directory, check if the input too
285 -- is a directory.
286
287 if Is_Directory (Get_Name_String (Infile_Name)) then
288 Input_Directory := Infile_Name;
289 end if;
290 end if;
291
292 -- And process the single input or the files in the directory tree
293 -- rooted at the input directory.
294
295 Process_Files;
296 end Gnatprep;
297
298 ---------------------
299 -- Is_ASCII_Letter --
300 ---------------------
301
302 function Is_ASCII_Letter (C : Character) return Boolean is
303 begin
304 return C in 'A' .. 'Z' or else C in 'a' .. 'z';
305 end Is_ASCII_Letter;
306
307 ------------------------
308 -- New_EOL_To_Outfile --
309 ------------------------
310
311 procedure New_EOL_To_Outfile is
312 begin
313 New_Line (Outfile.all);
314 end New_EOL_To_Outfile;
315
316 ---------------
317 -- Post_Scan --
318 ---------------
319
320 procedure Post_Scan is
321 begin
322 null;
323 end Post_Scan;
324
325 ----------------------------
326 -- Preprocess_Infile_Name --
327 ----------------------------
328
329 procedure Preprocess_Infile_Name is
330 Len : Natural;
331 First : Positive;
332 Last : Natural;
333 Symbol : Name_Id;
334 Data : Symbol_Data;
335
336 begin
337 -- Initialize the buffer with the name of the input file
338
339 Get_Name_String (Infile_Name);
340 Len := Name_Len;
341
342 while File_Name_Buffer'Length < Len loop
343 Double_File_Name_Buffer;
344 end loop;
345
346 File_Name_Buffer (1 .. Len) := Name_Buffer (1 .. Len);
347
348 -- Look for possible symbols in the file name
349
350 First := 1;
351 while First < Len loop
352
353 -- A symbol starts with a dollar sign followed by a letter
354
355 if File_Name_Buffer (First) = '$' and then
356 Is_ASCII_Letter (File_Name_Buffer (First + 1))
357 then
358 Last := First + 1;
359
360 -- Find the last letter of the symbol
361
362 while Last < Len and then
363 Is_ASCII_Letter (File_Name_Buffer (Last + 1))
364 loop
365 Last := Last + 1;
366 end loop;
367
368 -- Get the symbol name id
369
370 Name_Len := Last - First;
371 Name_Buffer (1 .. Name_Len) :=
372 File_Name_Buffer (First + 1 .. Last);
373 To_Lower (Name_Buffer (1 .. Name_Len));
374 Symbol := Name_Find;
375
376 -- And look for this symbol name in the symbol table
377
378 for Index in 1 .. Symbol_Table.Last (Mapping) loop
379 Data := Mapping.Table (Index);
380
381 if Data.Symbol = Symbol then
382
383 -- We found the symbol. If its value is not a string,
384 -- replace the symbol in the file name with the value of
385 -- the symbol.
386
387 if not Data.Is_A_String then
388 String_To_Name_Buffer (Data.Value);
389
390 declare
391 Sym_Len : constant Positive := Last - First + 1;
392 Offset : constant Integer := Name_Len - Sym_Len;
393 New_Len : constant Natural := Len + Offset;
394
395 begin
396 while New_Len > File_Name_Buffer'Length loop
397 Double_File_Name_Buffer;
398 end loop;
399
400 File_Name_Buffer (Last + 1 + Offset .. New_Len) :=
401 File_Name_Buffer (Last + 1 .. Len);
402 Len := New_Len;
403 Last := Last + Offset;
404 File_Name_Buffer (First .. Last) :=
405 Name_Buffer (1 .. Name_Len);
406 end;
407 end if;
408
409 exit;
410 end if;
411 end loop;
412
413 -- Skip over the symbol name or its value: we are not checking
414 -- for another symbol name in the value.
415
416 First := Last + 1;
417
418 else
419 First := First + 1;
420 end if;
421 end loop;
422
423 -- We now have the output file name in the buffer. Get the output
424 -- path and put it in Outfile_Name.
425
426 Get_Name_String (Output_Directory);
427 Add_Char_To_Name_Buffer (Directory_Separator);
428 Add_Str_To_Name_Buffer (File_Name_Buffer (1 .. Len));
429 Outfile_Name := Name_Find;
430 end Preprocess_Infile_Name;
431
432 --------------------------------------------
433 -- Process_Command_Line_Symbol_Definition --
434 --------------------------------------------
435
436 procedure Process_Command_Line_Symbol_Definition (S : String) is
437 Data : Symbol_Data;
438 Symbol : Symbol_Id;
439
440 begin
441 -- Check the symbol definition and get the symbol and its value.
442 -- Fail if symbol definition is illegal.
443
444 Check_Command_Line_Symbol_Definition (S, Data);
445
446 Symbol := Index_Of (Data.Symbol);
447
448 -- If symbol does not already exist, create a new entry in the mapping
449 -- table.
450
451 if Symbol = No_Symbol then
452 Symbol_Table.Increment_Last (Mapping);
453 Symbol := Symbol_Table.Last (Mapping);
454 end if;
455
456 Mapping.Table (Symbol) := Data;
457 end Process_Command_Line_Symbol_Definition;
458
459 -------------------
460 -- Process_Files --
461 -------------------
462
463 procedure Process_Files is
464
465 procedure Process_One_File;
466 -- Process input file Infile_Name and put the result in file
467 -- Outfile_Name.
468
469 procedure Recursive_Process (In_Dir : String; Out_Dir : String);
470 -- Process recursively files in In_Dir. Results go to Out_Dir
471
472 ----------------------
473 -- Process_One_File --
474 ----------------------
475
476 procedure Process_One_File is
477 Infile : Source_File_Index;
478
479 Modified : Boolean;
480 pragma Warnings (Off, Modified);
481
482 begin
483 -- Create the output file (fails if this does not work)
484
485 begin
486 Create
487 (File => Text_Outfile,
488 Mode => Out_File,
489 Name => Get_Name_String (Outfile_Name),
490 Form => "Text_Translation=" &
491 Yes_No (Unix_Line_Terminators).all);
492
493 exception
494 when others =>
495 Fail
496 ("unable to create output file """
497 & Get_Name_String (Outfile_Name)
498 & """");
499 end;
500
501 -- Load the input file
502
503 Infile := Sinput.C.Load_File (Get_Name_String (Infile_Name));
504
505 if Infile = No_Source_File then
506 Fail ("unable to find input file """
507 & Get_Name_String (Infile_Name)
508 & """");
509 end if;
510
511 -- Set Main_Source_File to the input file for the benefit of
512 -- Errutil.Finalize.
513
514 Sinput.Main_Source_File := Infile;
515
516 Scanner.Initialize_Scanner (Infile);
517
518 -- Output the pragma Source_Reference if asked to
519
520 if Source_Ref_Pragma then
521 Put_Line
522 (Outfile.all,
523 "pragma Source_Reference (1, """ &
524 Get_Name_String (Sinput.Full_File_Name (Infile)) & """);");
525 end if;
526
527 -- Preprocess the input file
528
529 Prep.Preprocess (Modified);
530
531 -- In verbose mode, if there is no error, report it
532
533 if Opt.Verbose_Mode and then Total_Errors_Detected = 0 then
534 Errutil.Finalize (Source_Type => "input");
535 end if;
536
537 -- If we had some errors, delete the output file, and report them
538
539 if Total_Errors_Detected > 0 then
540 if Outfile /= Standard_Output then
541 Delete (Text_Outfile);
542 end if;
543
544 Errutil.Finalize (Source_Type => "input");
545
546 OS_Exit (0);
547
548 -- Otherwise, close the output file, and we are done
549
550 elsif Outfile /= Standard_Output then
551 Close (Text_Outfile);
552 end if;
553 end Process_One_File;
554
555 -----------------------
556 -- Recursive_Process --
557 -----------------------
558
559 procedure Recursive_Process (In_Dir : String; Out_Dir : String) is
560 Dir_In : Dir_Type;
561 Name : String (1 .. 255);
562 Last : Natural;
563 In_Dir_Name : Name_Id;
564 Out_Dir_Name : Name_Id;
565
566 procedure Set_Directory_Names;
567 -- Establish or reestablish the current input and output directories
568
569 -------------------------
570 -- Set_Directory_Names --
571 -------------------------
572
573 procedure Set_Directory_Names is
574 begin
575 Input_Directory := In_Dir_Name;
576 Output_Directory := Out_Dir_Name;
577 end Set_Directory_Names;
578
579 -- Start of processing for Recursive_Process
580
581 begin
582 -- Open the current input directory
583
584 begin
585 Open (Dir_In, In_Dir);
586
587 exception
588 when Directory_Error =>
589 Fail ("could not read directory " & In_Dir);
590 end;
591
592 -- Set the new input and output directory names
593
594 Name_Len := In_Dir'Length;
595 Name_Buffer (1 .. Name_Len) := In_Dir;
596 In_Dir_Name := Name_Find;
597 Name_Len := Out_Dir'Length;
598 Name_Buffer (1 .. Name_Len) := Out_Dir;
599 Out_Dir_Name := Name_Find;
600
601 Set_Directory_Names;
602
603 -- Traverse the input directory
604 loop
605 Read (Dir_In, Name, Last);
606 exit when Last = 0;
607
608 if Name (1 .. Last) /= "." and then Name (1 .. Last) /= ".." then
609 declare
610 Input : constant String :=
611 In_Dir & Directory_Separator & Name (1 .. Last);
612 Output : constant String :=
613 Out_Dir & Directory_Separator & Name (1 .. Last);
614
615 begin
616 -- If input is an ordinary file, process it
617
618 if Is_Regular_File (Input) then
619 -- First get the output file name
620
621 Name_Len := Last;
622 Name_Buffer (1 .. Name_Len) := Name (1 .. Last);
623 Infile_Name := Name_Find;
624 Preprocess_Infile_Name;
625
626 -- Set the input file name and process the file
627
628 Name_Len := Input'Length;
629 Name_Buffer (1 .. Name_Len) := Input;
630 Infile_Name := Name_Find;
631 Process_One_File;
632
633 elsif Is_Directory (Input) then
634 -- Input is a directory. If the corresponding output
635 -- directory does not already exist, create it.
636
637 if not Is_Directory (Output) then
638 begin
639 Make_Dir (Dir_Name => Output);
640
641 exception
642 when Directory_Error =>
643 Fail ("could not create directory """
644 & Output
645 & """");
646 end;
647 end if;
648
649 -- And process this new input directory
650
651 Recursive_Process (Input, Output);
652
653 -- Reestablish the input and output directory names
654 -- that have been modified by the recursive call.
655
656 Set_Directory_Names;
657 end if;
658 end;
659 end if;
660 end loop;
661 end Recursive_Process;
662
663 -- Start of processing for Process_Files
664
665 begin
666 if Output_Directory = No_Name then
667
668 -- If the output is not a directory, fail if the input is
669 -- an existing directory, to avoid possible problems.
670
671 if Is_Directory (Get_Name_String (Infile_Name)) then
672 Fail ("input file """ & Get_Name_String (Infile_Name) &
673 """ is a directory");
674 end if;
675
676 -- Just process the single input file
677
678 Process_One_File;
679
680 elsif Input_Directory = No_Name then
681
682 -- Get the output file name from the input file name, and process
683 -- the single input file.
684
685 Preprocess_Infile_Name;
686 Process_One_File;
687
688 else
689 -- Recursively process files in the directory tree rooted at the
690 -- input directory.
691
692 Recursive_Process
693 (In_Dir => Get_Name_String (Input_Directory),
694 Out_Dir => Get_Name_String (Output_Directory));
695 end if;
696 end Process_Files;
697
698 -------------------------
699 -- Put_Char_To_Outfile --
700 -------------------------
701
702 procedure Put_Char_To_Outfile (C : Character) is
703 begin
704 Put (Outfile.all, C);
705 end Put_Char_To_Outfile;
706
707 -----------------------
708 -- Scan_Command_Line --
709 -----------------------
710
711 procedure Scan_Command_Line is
712 Switch : Character;
713
714 procedure Check_Version_And_Help is new Check_Version_And_Help_G (Usage);
715
716 -- Start of processing for Scan_Command_Line
717
718 begin
719 -- First check for --version or --help
720
721 Check_Version_And_Help ("GNATPREP", "1996");
722
723 -- Now scan the other switches
724
725 GNAT.Command_Line.Initialize_Option_Scan;
726
727 loop
728 begin
729 Switch := GNAT.Command_Line.Getopt ("D: a b c C r s T u v");
730
731 case Switch is
732
733 when ASCII.NUL =>
734 exit;
735
736 when 'D' =>
737 Process_Command_Line_Symbol_Definition
738 (S => GNAT.Command_Line.Parameter);
739
740 when 'a' =>
741 Opt.No_Deletion := True;
742 Opt.Undefined_Symbols_Are_False := True;
743
744 when 'b' =>
745 Opt.Blank_Deleted_Lines := True;
746
747 when 'c' =>
748 Opt.Comment_Deleted_Lines := True;
749
750 when 'C' =>
751 Opt.Replace_In_Comments := True;
752
753 when 'r' =>
754 Source_Ref_Pragma := True;
755
756 when 's' =>
757 Opt.List_Preprocessing_Symbols := True;
758
759 when 'T' =>
760 Unix_Line_Terminators := True;
761
762 when 'u' =>
763 Opt.Undefined_Symbols_Are_False := True;
764
765 when 'v' =>
766 Opt.Verbose_Mode := True;
767
768 when others =>
769 Fail ("Invalid Switch: -" & Switch);
770 end case;
771
772 exception
773 when GNAT.Command_Line.Invalid_Switch =>
774 Write_Str ("Invalid Switch: -");
775 Write_Line (GNAT.Command_Line.Full_Switch);
776 GNAT.Command_Line.Try_Help;
777 OS_Exit (1);
778 end;
779 end loop;
780
781 -- Get the file names
782
783 loop
784 declare
785 S : constant String := GNAT.Command_Line.Get_Argument;
786
787 begin
788 exit when S'Length = 0;
789
790 Name_Len := S'Length;
791 Name_Buffer (1 .. Name_Len) := S;
792
793 if Infile_Name = No_Name then
794 Infile_Name := Name_Find;
795 elsif Outfile_Name = No_Name then
796 Outfile_Name := Name_Find;
797 elsif Deffile_Name = No_Name then
798 Deffile_Name := Name_Find;
799 else
800 Fail ("too many arguments specified");
801 end if;
802 end;
803 end loop;
804 end Scan_Command_Line;
805
806 -----------
807 -- Usage --
808 -----------
809
810 procedure Usage is
811 begin
812 Display_Copyright;
813 Write_Line ("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
814 "infile outfile [deffile]");
815 Write_Eol;
816 Write_Line (" infile Name of the input file");
817 Write_Line (" outfile Name of the output file");
818 Write_Line (" deffile Name of the definition file");
819 Write_Eol;
820 Write_Line ("gnatprep switches:");
821 Display_Usage_Version_And_Help;
822 Write_Line (" -b Replace preprocessor lines by blank lines");
823 Write_Line (" -c Keep preprocessor lines as comments");
824 Write_Line (" -C Do symbol replacements within comments");
825 Write_Line (" -D Associate symbol with value");
826 Write_Line (" -r Generate Source_Reference pragma");
827 Write_Line (" -s Print a sorted list of symbol names and values");
828 Write_Line (" -T Use LF as line terminators");
829 Write_Line (" -u Treat undefined symbols as FALSE");
830 Write_Line (" -v Verbose mode");
831 Write_Eol;
832 end Usage;
833
834 end GPrep;