2008-05-30 Vladimir Makarov <vmakarov@redhat.com>
[official-gcc.git] / gcc / ada / xgnatugn.adb
blobee3b07df82052b2bc96e21aa7ea3ac7142a45dee
1 ------------------------------------------------------------------------------
2 -- --
3 -- GNAT SYSTEM UTILITIES --
4 -- --
5 -- X G N A T U G N --
6 -- --
7 -- B o d y --
8 -- --
9 -- Copyright (C) 2003-2008, 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 ------------------------------------------------------------------------------
23 -- This utility is used to process the source of gnat_ugn.texi to make a
24 -- version suitable for running through standard Texinfo processor. It is
25 -- invoked as follows:
27 -- xgnatugn <target> <in-file> <word-list> [ <out-file> [ <warnings> ] ]
29 -- 1. <target> is the target type of the manual, which is one of:
31 -- unw Unix and Windows platforms
32 -- vms OpenVMS
34 -- 2. <in-file> is the file name of the Texinfo file to be
35 -- preprocessed.
37 -- 3. <word-list> is the name of the word list file. This file is used for
38 -- rewriting the VMS edition. Each line contains a word mapping: The source
39 -- word in the first column, the target word in the second column. The
40 -- columns are separated by a '^' character. When preprocessing for VMS, the
41 -- first word is replaced with the second. (Words consist of letters,
42 -- digits, and the four characters "?-_~". A sequence of multiple words can
43 -- be replaced if they are listed in the first column, separated by a single
44 -- space character. If multiple words are to be replaced, there must be a
45 -- replacement for each prefix.)
47 -- 4. <out-file> (optional) is the name of the output file. It defaults to
48 -- gnat_ugn_unw.texi or gnat_ugn_vms.texi, depending on the target.
50 -- 5. <warnings> (optional, and allowed only if <out-file> is explicit)
51 -- can be any string. If present, it indicates that warning messages are
52 -- to be output to Standard_Error. If absent, no warning messages are
53 -- generated.
55 -- The following steps are performed:
57 -- In VMS mode
59 -- Any occurrences of ^alpha^beta^ are replaced by beta. The sequence
60 -- must fit on a single line, and there can only be one occurrence on a
61 -- line.
63 -- Any occurrences of a word in the Ug_Words list are replaced by the
64 -- appropriate vms equivalents. Note that replacements do not occur
65 -- within ^alpha^beta^ sequences.
67 -- Any occurrence of [filename].extension, where extension one of the
68 -- following:
70 -- "o", "ads", "adb", "ali", "ada", "atb", "ats", "adc", "c"
72 -- replaced by the appropriate VMS names (all upper case with .o
73 -- replaced .OBJ). Note that replacements do not occur within
74 -- ^alpha^beta^ sequences.
76 -- In UNW mode
78 -- Any occurrences of ^alpha^beta^ are replaced by alpha. The sequence
79 -- must fit on a single line.
81 -- In both modes
83 -- The sequence ^^^ is replaced by a single ^. This escape sequence
84 -- must be used if the literal character ^ is to appear in the
85 -- output. A line containing this escape sequence may not also contain
86 -- a ^alpha^beta^ sequence.
88 -- Process @ifset and @ifclear for the target flags (unw, vms);
89 -- this is because we have menu problems if we let makeinfo handle
90 -- these ifset/ifclear pairs.
91 -- Note: @ifset/@ifclear commands for the edition flags (FSFEDITION,
92 -- PROEDITION, GPLEDITION) are passed through unchanged
94 with Ada.Command_Line; use Ada.Command_Line;
95 with Ada.Strings; use Ada.Strings;
96 with Ada.Strings.Fixed; use Ada.Strings.Fixed;
97 with Ada.Strings.Unbounded; use Ada.Strings.Unbounded;
98 with Ada.Strings.Maps; use Ada.Strings.Maps;
99 with Ada.Strings.Maps.Constants; use Ada.Strings.Maps.Constants;
100 with Ada.Streams.Stream_IO; use Ada.Streams.Stream_IO;
101 with Ada.Text_IO; use Ada.Text_IO;
103 with GNAT.Spitbol; use GNAT.Spitbol;
104 with GNAT.Spitbol.Table_VString; use GNAT.Spitbol.Table_VString;
106 procedure Xgnatugn is
108 procedure Usage;
109 -- Print usage information. Invoked if an invalid command line is
110 -- encountered.
112 subtype Sfile is Ada.Streams.Stream_IO.File_Type;
114 Output_File : Sfile;
115 -- The preprocessed output is written to this file
117 type Input_File is record
118 Name : VString;
119 Data : Ada.Text_IO.File_Type;
120 Line : Natural := 0;
121 end record;
122 -- Records information on an input file. Name and Line are used
123 -- in error messages, Line is updated automatically by Get_Line.
125 function Get_Line (Input : access Input_File) return String;
126 -- Returns a line from Input and performs the necessary
127 -- line-oriented checks (length, character set, trailing spaces).
129 procedure Put_Line (F : Sfile; S : String);
130 procedure Put_Line (F : Sfile; S : VString);
131 -- Local version of Put_Line ensures Unix style line endings
133 Number_Of_Warnings : Natural := 0;
134 Number_Of_Errors : Natural := 0;
135 Warnings_Enabled : Boolean;
137 procedure Error
138 (Input : Input_File;
139 At_Character : Natural;
140 Message : String);
141 procedure Error
142 (Input : Input_File;
143 Message : String);
144 -- Prints a message reporting an error on line Input.Line. If
145 -- At_Character is not 0, indicate the exact character at which
146 -- the error occurs.
148 procedure Warning
149 (Input : Input_File;
150 At_Character : Natural;
151 Message : String);
152 procedure Warning
153 (Input : Input_File;
154 Message : String);
155 -- Like Error, but just print a warning message
157 Dictionary_File : aliased Input_File;
158 procedure Read_Dictionary_File;
159 -- Dictionary_File is opened using the name given on the command
160 -- line. It contains the replacements for the Ug_Words list.
161 -- Read_Dictionary_File reads Dictionary_File and fills the
162 -- Ug_Words table.
164 Source_File : aliased Input_File;
165 procedure Process_Source_File;
166 -- Source_File is opened using the name given on the command line.
167 -- It contains the Texinfo source code. Process_Source_File
168 -- performs the necessary replacements.
170 type Flag_Type is (UNW, VMS, FSFEDITION, PROEDITION, GPLEDITION);
171 -- The flags permitted in @ifset or @ifclear commands:
173 -- Targets for preprocessing
174 -- UNW (Unix and Windows) or VMS
176 -- Editions of the manual
177 -- FSFEDITION, PROEDITION, or GPLEDITION
179 -- Conditional commands for target are processed by xgnatugn
181 -- Conditional commands for edition are passed through unchanged
183 subtype Target_Type is Flag_Type range UNW .. VMS;
184 subtype Edition_Type is Flag_Type range FSFEDITION .. GPLEDITION;
186 Target : Target_Type;
187 -- The Target variable is initialized using the command line
189 Valid_Characters : constant Character_Set :=
190 To_Set (Span => (' ', '~'));
191 -- This array controls which characters are permitted in the input
192 -- file (after line breaks have been removed). Valid characters
193 -- are all printable ASCII characters and the space character.
195 Word_Characters : constant Character_Set :=
196 (To_Set (Ranges =>
197 (('0', '9'), ('a', 'z'), ('A', 'Z')))
198 or To_Set ("?-_~"));
199 -- The characters which are permitted in words. Other (valid)
200 -- characters are assumed to be delimiters between words. Note that
201 -- this set has to include all characters of the source words of the
202 -- Ug_Words dictionary.
204 Reject_Trailing_Spaces : constant Boolean := True;
205 -- Controls whether Xgnatug rejects superfluous space characters
206 -- at the end of lines.
208 Maximum_Line_Length : constant Positive := 79;
209 Fatal_Line_Length_Limit : constant Positive := 5000;
210 Fatal_Line_Length : exception;
211 -- If Maximum_Line_Length is exceeded in an input file, an error
212 -- message is printed. If Fatal_Line_Length is exceeded,
213 -- execution terminates with a Fatal_Line_Length exception.
215 VMS_Escape_Character : constant Character := '^';
216 -- The character used to mark VMS alternatives (^alpha^beta^)
218 Extensions : GNAT.Spitbol.Table_VString.Table (20);
219 procedure Initialize_Extensions;
220 -- This table records extensions and their replacement for
221 -- rewriting filenames in the VMS version of the manual.
223 function Is_Extension (Extension : String) return Boolean;
224 function Get_Replacement_Extension (Extension : String) return String;
225 -- These functions query the replacement table. Is_Extension
226 -- checks if the given string is a known extension.
227 -- Get_Replacement returns the replacement extension.
229 Ug_Words : GNAT.Spitbol.Table_VString.Table (200);
230 function Is_Known_Word (Word : String) return Boolean;
231 function Get_Replacement_Word (Word : String) return String;
232 -- The Ug_Words table lists replacement words for the VMS version
233 -- of the manual. Is_Known_Word and Get_Replacement_Word query
234 -- this table. The table is filled using Read_Dictionary_File.
236 function Rewrite_Source_Line (Line : String) return String;
237 -- This subprogram takes a line and rewrites it according to Target.
238 -- It relies on information in Source_File to generate error messages.
240 type Conditional is (Set, Clear);
241 procedure Push_Conditional (Cond : Conditional; Flag : Target_Type);
242 procedure Pop_Conditional (Cond : Conditional);
243 -- These subprograms deal with conditional processing (@ifset/@ifclear).
244 -- They rely on information in Source_File to generate error messages.
246 function Currently_Excluding return Boolean;
247 -- Returns true if conditional processing directives imply that the
248 -- current line should not be included in the output.
250 function VMS_Context_Determined return Boolean;
251 -- Returns true if, in the current conditional preprocessing context, we
252 -- always have a VMS or a non-VMS version, regardless of the value of
253 -- Target.
255 function In_VMS_Section return Boolean;
256 -- Returns True if in an "@ifset vms" section
258 procedure Check_No_Pending_Conditional;
259 -- Checks that all preprocessing directives have been properly matched by
260 -- their @end counterpart. If this is not the case, print an error
261 -- message.
263 -- The following definitions implement a stack to track the conditional
264 -- preprocessing context.
266 type Conditional_Context is record
267 Starting_Line : Positive;
268 Cond : Conditional;
269 Flag : Flag_Type;
270 Excluding : Boolean;
271 end record;
273 Conditional_Stack_Depth : constant := 3;
275 Conditional_Stack :
276 array (1 .. Conditional_Stack_Depth) of Conditional_Context;
278 Conditional_TOS : Natural := 0;
279 -- Pointer to the Top Of Stack for Conditional_Stack
281 -----------
282 -- Usage --
283 -----------
285 procedure Usage is
286 begin
287 Put_Line (Standard_Error,
288 "usage: xgnatugn TARGET SOURCE DICTIONARY [OUTFILE [WARNINGS]]");
289 New_Line;
290 Put_Line (Standard_Error, "TARGET is one of:");
292 for T in Target_Type'Range loop
293 Put_Line (Standard_Error, " " & Target_Type'Image (T));
294 end loop;
296 New_Line;
297 Put_Line (Standard_Error, "SOURCE is the source file to process.");
298 New_Line;
299 Put_Line (Standard_Error, "DICTIONARY is the name of a file "
300 & "that contains word replacements");
301 Put_Line (Standard_Error, "for the VMS version.");
302 New_Line;
303 Put_Line (Standard_Error,
304 "OUT-FILE, if present, is the output file to be created;");
305 Put_Line (Standard_Error,
306 "If OUT-FILE is absent, the output file is either " &
307 "gnat_ugn_unw.texi, ");
308 Put_Line (Standard_Error,
309 "or gnat_ugn_vms.texi, depending on TARGET.");
310 New_Line;
311 Put_Line (Standard_Error,
312 "WARNINGS, if present, is any string;");
313 Put_Line (Standard_Error,
314 "it will result in warning messages (e.g., line too long))");
315 Put_Line (Standard_Error,
316 "being output to Standard_Error.");
317 end Usage;
319 --------------
320 -- Get_Line --
321 --------------
323 function Get_Line (Input : access Input_File) return String is
324 Line_Buffer : String (1 .. Fatal_Line_Length_Limit);
325 Last : Natural;
327 begin
328 Input.Line := Input.Line + 1;
329 Get_Line (Input.Data, Line_Buffer, Last);
331 if Last = Line_Buffer'Last then
332 Error (Input.all, "line exceeds fatal line length limit");
333 raise Fatal_Line_Length;
334 end if;
336 declare
337 Line : String renames Line_Buffer (Line_Buffer'First .. Last);
339 begin
340 for J in Line'Range loop
341 if not Is_In (Line (J), Valid_Characters) then
342 Error (Input.all, J, "invalid character");
343 exit;
344 end if;
345 end loop;
347 if Line'Length > Maximum_Line_Length then
348 Warning (Input.all, Maximum_Line_Length + 1, "line too long");
349 end if;
351 if Reject_Trailing_Spaces
352 and then Line'Length > 0
353 and then Line (Line'Last) = ' '
354 then
355 Error (Input.all, Line'Last, "trailing space character");
356 end if;
358 return Trim (Line, Right);
359 end;
360 end Get_Line;
362 --------------
363 -- Put_Line --
364 --------------
366 procedure Put_Line (F : Sfile; S : String) is
367 begin
368 String'Write (Stream (F), S);
369 Character'Write (Stream (F), ASCII.LF);
370 end Put_Line;
372 procedure Put_Line (F : Sfile; S : VString) is
373 begin
374 Put_Line (F, To_String (S));
375 end Put_Line;
377 -----------
378 -- Error --
379 -----------
381 procedure Error
382 (Input : Input_File;
383 Message : String)
385 begin
386 Error (Input, 0, Message);
387 end Error;
389 procedure Error
390 (Input : Input_File;
391 At_Character : Natural;
392 Message : String)
394 Line_Image : constant String := Integer'Image (Input.Line);
395 At_Character_Image : constant String := Integer'Image (At_Character);
396 -- These variables are required because we have to drop the leading
397 -- space character.
399 begin
400 Number_Of_Errors := Number_Of_Errors + 1;
402 if At_Character > 0 then
403 Put_Line (Standard_Error,
404 S (Input.Name) & ':'
405 & Line_Image (Line_Image'First + 1 .. Line_Image'Last) & ':'
406 & At_Character_Image (At_Character_Image'First + 1
407 .. At_Character_Image'Last)
408 & ": "
409 & Message);
410 else
411 Put_Line (Standard_Error,
412 S (Input.Name) & ':'
413 & Line_Image (Line_Image'First + 1 .. Line_Image'Last)
414 & ": "
415 & Message);
416 end if;
417 end Error;
419 -------------
420 -- Warning --
421 -------------
423 procedure Warning
424 (Input : Input_File;
425 Message : String)
427 begin
428 if Warnings_Enabled then
429 Warning (Input, 0, Message);
430 end if;
431 end Warning;
433 procedure Warning
434 (Input : Input_File;
435 At_Character : Natural;
436 Message : String)
438 Line_Image : constant String := Integer'Image (Input.Line);
439 At_Character_Image : constant String := Integer'Image (At_Character);
440 -- These variables are required because we have to drop the leading
441 -- space character.
443 begin
444 if not Warnings_Enabled then
445 return;
446 end if;
448 Number_Of_Warnings := Number_Of_Warnings + 1;
450 if At_Character > 0 then
451 Put_Line (Standard_Error,
452 S (Input.Name) & ':'
453 & Line_Image (Line_Image'First + 1 .. Line_Image'Last) & ':'
454 & At_Character_Image (At_Character_Image'First + 1
455 .. At_Character_Image'Last)
456 & ": warning: "
457 & Message);
458 else
459 Put_Line (Standard_Error,
460 S (Input.Name) & ':'
461 & Line_Image (Line_Image'First + 1 .. Line_Image'Last)
462 & ": warning: "
463 & Message);
464 end if;
465 end Warning;
467 --------------------------
468 -- Read_Dictionary_File --
469 --------------------------
471 procedure Read_Dictionary_File is
472 begin
473 while not End_Of_File (Dictionary_File.Data) loop
474 declare
475 Line : constant String :=
476 Get_Line (Dictionary_File'Access);
477 Split : constant Natural :=
478 Index (Line, (1 => VMS_Escape_Character));
480 begin
481 if Line'Length = 0 then
482 Error (Dictionary_File, "empty line in dictionary file");
484 elsif Line (Line'First) = ' ' then
485 Error (Dictionary_File, 1, "line starts with space character");
487 elsif Split = 0 then
488 Error (Dictionary_File, "line does not contain "
489 & VMS_Escape_Character & " character");
490 else
491 declare
492 Source : constant String :=
493 Trim (Line (1 .. Split - 1), Both);
494 Target : constant String :=
495 Trim (Line (Split + 1 .. Line'Last), Both);
496 Two_Spaces : constant Natural :=
497 Index (Source, " ");
498 Non_Word_Character : constant Natural :=
499 Index (Source,
500 Word_Characters or
501 To_Set (" ."),
502 Outside);
504 begin
505 if Two_Spaces /= 0 then
506 Error (Dictionary_File, Two_Spaces,
507 "multiple space characters in source word");
508 end if;
510 if Non_Word_Character /= 0 then
511 Error (Dictionary_File, Non_Word_Character,
512 "illegal character in source word");
513 end if;
515 if Source'Length = 0 then
516 Error (Dictionary_File, "source is empty");
518 elsif Target'Length = 0 then
519 Error (Dictionary_File, "target is empty");
521 else
522 Set (Ug_Words, Source, V (Target));
524 -- Ensure that if Source is a sequence of words
525 -- "WORD1 WORD2 ...", we already have a mapping for
526 -- "WORD1".
528 for J in Source'Range loop
529 if Source (J) = ' ' then
530 declare
531 Prefix : String renames
532 Source (Source'First .. J - 1);
534 begin
535 if not Is_Known_Word (Prefix) then
536 Error (Dictionary_File,
537 "prefix '" & Prefix
538 & "' not known at this point");
539 end if;
540 end;
541 end if;
542 end loop;
543 end if;
544 end;
545 end if;
546 end;
547 end loop;
548 end Read_Dictionary_File;
550 -------------------------
551 -- Rewrite_Source_Line --
552 -------------------------
554 function Rewrite_Source_Line (Line : String) return String is
556 -- We use a simple lexer to split the line into tokens:
558 -- Word consisting entirely of Word_Characters
559 -- VMS_Alternative ^alpha^beta^ replacement (but not ^^^)
560 -- Space a space character
561 -- Other everything else (sequence of non-word characters)
562 -- VMS_Error incomplete VMS alternative
563 -- End_Of_Line no more characters on this line
565 -- A sequence of three VMS_Escape_Characters is automatically
566 -- collapsed to an Other token.
568 type Token_Span is record
569 First, Last : Positive;
570 end record;
571 -- The character range covered by a token in Line
573 type Token_Kind is (End_Of_Line, Word, Other,
574 VMS_Alternative, VMS_Error);
575 type Token_Record (Kind : Token_Kind := End_Of_Line) is record
576 First : Positive;
577 case Kind is
578 when Word | Other =>
579 Span : Token_Span;
580 when VMS_Alternative =>
581 Non_VMS, VMS : Token_Span;
582 when VMS_Error | End_Of_Line =>
583 null;
584 end case;
585 end record;
587 Input_Position : Positive := Line'First;
588 Token : Token_Record;
589 -- The position of the next character to be processed by Next_Token
591 procedure Next_Token;
592 -- Returns the next token in Line, starting at Input_Position
594 Rewritten_Line : VString;
595 -- Collects the line as it is rewritten
597 procedure Rewrite_Word;
598 -- The current token is assumed to be a Word. When processing the VMS
599 -- version of the manual, additional tokens are gathered to check if
600 -- we have a file name or a sequence of known words.
602 procedure Maybe_Rewrite_Extension;
603 -- The current token is assumed to be Other. When processing the VMS
604 -- version of the manual and the token represents a single dot ".",
605 -- the following word is rewritten according to the rules for
606 -- extensions.
608 VMS_Token_Seen : Boolean := False;
609 -- This is set to true if a VMS_Alternative has been encountered, or a
610 -- ^^^ token.
612 ----------------
613 -- Next_Token --
614 ----------------
616 procedure Next_Token is
617 Remaining_Line : String renames Line (Input_Position .. Line'Last);
618 Last_Character : Natural;
620 begin
621 if Remaining_Line'Length = 0 then
622 Token := (End_Of_Line, Remaining_Line'First);
623 return;
624 end if;
626 -- ^alpha^beta^, the VMS_Alternative case
628 if Remaining_Line (Remaining_Line'First) = VMS_Escape_Character then
629 declare
630 VMS_Second_Character, VMS_Third_Character : Natural;
632 begin
633 if VMS_Token_Seen then
634 Error (Source_File, Remaining_Line'First,
635 "multiple " & VMS_Escape_Character
636 & " characters on a single line");
637 else
638 VMS_Token_Seen := True;
639 end if;
641 -- Find the second and third escape character. If one of
642 -- them is not present, generate an error token.
644 VMS_Second_Character :=
645 Index (Remaining_Line (Remaining_Line'First + 1
646 .. Remaining_Line'Last),
647 (1 => VMS_Escape_Character));
649 if VMS_Second_Character = 0 then
650 Input_Position := Remaining_Line'Last + 1;
651 Token := (VMS_Error, Remaining_Line'First);
652 return;
653 end if;
655 VMS_Third_Character :=
656 Index (Remaining_Line (VMS_Second_Character + 1
657 .. Remaining_Line'Last),
658 (1 => VMS_Escape_Character));
660 if VMS_Third_Character = 0 then
661 Input_Position := Remaining_Line'Last + 1;
662 Token := (VMS_Error, Remaining_Line'First);
663 return;
664 end if;
666 -- Consume all the characters we are about to include in
667 -- the token.
669 Input_Position := VMS_Third_Character + 1;
671 -- Check if we are in a ^^^ situation, and return an Other
672 -- token in this case.
674 if Remaining_Line'First + 1 = VMS_Second_Character
675 and then Remaining_Line'First + 2 = VMS_Third_Character
676 then
677 Token := (Other, Remaining_Line'First,
678 (Remaining_Line'First, Remaining_Line'First));
679 return;
680 end if;
682 Token := (VMS_Alternative, Remaining_Line'First,
683 (Remaining_Line'First + 1, VMS_Second_Character - 1),
684 (VMS_Second_Character + 1, VMS_Third_Character - 1));
685 return;
686 end;
687 end if; -- VMS_Alternative
689 -- The Word case. Search for characters not in Word_Characters.
690 -- We have found a word if the first non-word character is not
691 -- the first character in Remaining_Line, i.e. if Remaining_Line
692 -- starts with a word character.
694 Last_Character := Index (Remaining_Line, Word_Characters, Outside);
695 if Last_Character /= Remaining_Line'First then
697 -- If we haven't found a character which is not in
698 -- Word_Characters, all remaining characters are part of the
699 -- current Word token.
701 if Last_Character = 0 then
702 Last_Character := Remaining_Line'Last + 1;
703 end if;
705 Input_Position := Last_Character;
706 Token := (Word, Remaining_Line'First,
707 (Remaining_Line'First, Last_Character - 1));
708 return;
709 end if;
711 -- Remaining characters are in the Other category. To speed
712 -- up processing, we collect them together if there are several
713 -- of them.
715 Input_Position := Last_Character + 1;
716 Token := (Other,
717 Remaining_Line'First,
718 (Remaining_Line'First, Last_Character));
719 end Next_Token;
721 ------------------
722 -- Rewrite_Word --
723 ------------------
725 procedure Rewrite_Word is
726 First_Word : String
727 renames Line (Token.Span.First .. Token.Span.Last);
729 begin
730 -- We do not perform any error checking below, so we can just skip
731 -- all processing for the non-VMS version.
733 if Target /= VMS then
734 Append (Rewritten_Line, First_Word);
735 Next_Token;
736 return;
737 end if;
739 if Is_Known_Word (First_Word) then
741 -- If we have a word from the dictionary, we look for the
742 -- longest possible sequence we can rewrite.
744 declare
745 Seq : Token_Span := Token.Span;
746 Lost_Space : Boolean := False;
748 begin
749 Next_Token;
750 loop
751 if Token.Kind = Other
752 and then Line (Token.Span.First .. Token.Span.Last) = " "
753 then
754 Next_Token;
755 if Token.Kind /= Word
756 or else not Is_Known_Word (Line (Seq.First
757 .. Token.Span.Last))
758 then
759 -- When we reach this point, the following
760 -- conditions are true:
762 -- Seq is a known word.
763 -- The previous token was a space character.
764 -- Seq extended to the current token is not a
765 -- known word.
767 Lost_Space := True;
768 exit;
770 else
772 -- Extend Seq to cover the current (known) word
774 Seq.Last := Token.Span.Last;
775 Next_Token;
776 end if;
778 else
779 -- When we reach this point, the following conditions
780 -- are true:
782 -- Seq is a known word.
783 -- The previous token was a word.
784 -- The current token is not a space character.
786 exit;
787 end if;
788 end loop;
790 -- Rewrite Seq, and add the lost space if necessary
792 Append (Rewritten_Line,
793 Get_Replacement_Word (Line (Seq.First .. Seq.Last)));
794 if Lost_Space then
795 Append (Rewritten_Line, ' ');
796 end if;
798 -- The unknown token will be processed during the
799 -- next iteration of the main loop.
800 return;
801 end;
802 end if;
804 Next_Token;
806 if Token.Kind = Other
807 and then Line (Token.Span.First .. Token.Span.Last) = "."
808 then
809 -- Deal with extensions
811 Next_Token;
812 if Token.Kind = Word
813 and then Is_Extension (Line (Token.Span.First
814 .. Token.Span.Last))
815 then
816 -- We have discovered a file extension. Convert the file
817 -- name to upper case.
819 Append (Rewritten_Line,
820 Translate (First_Word, Upper_Case_Map) & '.');
821 Append (Rewritten_Line,
822 Get_Replacement_Extension
823 (Line (Token.Span.First .. Token.Span.Last)));
824 Next_Token;
825 else
826 -- We already have: Word ".", followed by an unknown token
828 Append (Rewritten_Line, First_Word & '.');
830 -- The unknown token will be processed during the next
831 -- iteration of the main loop.
832 end if;
834 else
835 -- We have an unknown Word, followed by an unknown token.
836 -- The unknown token will be processed by the outer loop.
838 Append (Rewritten_Line, First_Word);
839 end if;
840 end Rewrite_Word;
842 -----------------------------
843 -- Maybe_Rewrite_Extension --
844 -----------------------------
846 procedure Maybe_Rewrite_Extension is
847 begin
848 -- Again, we need no special processing in the non-VMS case
850 if Target = VMS
851 and then Line (Token.Span.First .. Token.Span.Last) = "."
852 then
853 -- This extension is not preceded by a word, otherwise
854 -- Rewrite_Word would have handled it.
856 Next_Token;
857 if Token.Kind = Word
858 and then Is_Extension (Line (Token.Span.First
859 .. Token.Span.Last))
860 then
861 Append (Rewritten_Line, '.' & Get_Replacement_Extension
862 (Line (Token.Span.First .. Token.Span.Last)));
863 Next_Token;
864 else
865 Append (Rewritten_Line, '.');
866 end if;
867 else
868 Append (Rewritten_Line, Line (Token.Span.First
869 .. Token.Span.Last));
870 Next_Token;
871 end if;
872 end Maybe_Rewrite_Extension;
874 -- Start of processing for Process_Source_Line
876 begin
877 -- The following parser recognizes the following special token
878 -- sequences:
880 -- Word "." Word rewrite as file name if second word is extension
881 -- Word " " Word rewrite as a single word using Ug_Words table
883 Next_Token;
884 loop
885 case Token.Kind is
886 when End_Of_Line =>
887 exit;
889 when Word =>
890 Rewrite_Word;
892 when Other =>
893 Maybe_Rewrite_Extension;
895 when VMS_Alternative =>
896 if VMS_Context_Determined then
897 if (not In_VMS_Section)
898 or else
899 Line (Token.VMS.First .. Token.VMS.Last) /=
900 Line (Token.Non_VMS.First .. Token.Non_VMS.Last)
901 then
902 Warning (Source_File, Token.First,
903 "VMS alternative already determined "
904 & "by conditionals");
905 end if;
906 end if;
907 if Target = VMS then
908 Append (Rewritten_Line, Line (Token.VMS.First
909 .. Token.VMS.Last));
910 else
911 Append (Rewritten_Line, Line (Token.Non_VMS.First
912 .. Token.Non_VMS.Last));
913 end if;
914 Next_Token;
916 when VMS_Error =>
917 Error (Source_File, Token.First, "invalid VMS alternative");
918 Next_Token;
919 end case;
920 end loop;
922 return S (Rewritten_Line);
923 end Rewrite_Source_Line;
925 -------------------------
926 -- Process_Source_File --
927 -------------------------
929 procedure Process_Source_File is
930 Ifset : constant String := "@ifset ";
931 Ifclear : constant String := "@ifclear ";
932 Endsetclear : constant String := "@end ";
933 -- Strings to be recognized for conditional processing
935 begin
936 while not End_Of_File (Source_File.Data) loop
937 declare
938 Line : constant String := Get_Line (Source_File'Access);
939 Rewritten : constant String := Rewrite_Source_Line (Line);
940 -- We unconditionally rewrite the line so that we can check the
941 -- syntax of all lines, and not only those which are actually
942 -- included in the output.
944 Have_Conditional : Boolean := False;
945 -- True if we have encountered a conditional preprocessing
946 -- directive.
948 Cond : Conditional;
949 -- The kind of the directive
951 Flag : Flag_Type;
952 -- Its flag
954 begin
955 -- If the line starts with @ifset or @ifclear, we try to convert
956 -- the following flag to one of our flag types. If we fail,
957 -- Have_Conditional remains False.
959 if Line'Length >= Ifset'Length
960 and then Line (1 .. Ifset'Length) = Ifset
961 then
962 Cond := Set;
964 declare
965 Arg : constant String :=
966 Trim (Line (Ifset'Length + 1 .. Line'Last), Both);
968 begin
969 Flag := Flag_Type'Value (Arg);
970 Have_Conditional := True;
972 case Flag is
973 when Target_Type =>
974 if Translate (Target_Type'Image (Flag),
975 Lower_Case_Map)
976 /= Arg
977 then
978 Error (Source_File, "flag has to be lowercase");
979 end if;
981 when Edition_Type =>
982 null;
983 end case;
984 exception
985 when Constraint_Error =>
986 Error (Source_File, "unknown flag for '@ifset'");
987 end;
989 elsif Line'Length >= Ifclear'Length
990 and then Line (1 .. Ifclear'Length) = Ifclear
991 then
992 Cond := Clear;
994 declare
995 Arg : constant String :=
996 Trim (Line (Ifclear'Length + 1 .. Line'Last), Both);
998 begin
999 Flag := Flag_Type'Value (Arg);
1000 Have_Conditional := True;
1002 case Flag is
1003 when Target_Type =>
1004 if Translate (Target_Type'Image (Flag),
1005 Lower_Case_Map)
1006 /= Arg
1007 then
1008 Error (Source_File, "flag has to be lowercase");
1009 end if;
1011 when Edition_Type =>
1012 null;
1013 end case;
1014 exception
1015 when Constraint_Error =>
1016 Error (Source_File, "unknown flag for '@ifclear'");
1017 end;
1018 end if;
1020 if Have_Conditional and (Flag in Target_Type) then
1022 -- We create a new conditional context and suppress the
1023 -- directive in the output.
1025 Push_Conditional (Cond, Flag);
1027 elsif Line'Length >= Endsetclear'Length
1028 and then Line (1 .. Endsetclear'Length) = Endsetclear
1029 and then (Flag in Target_Type)
1030 then
1031 -- The '@end ifset'/'@end ifclear' case is handled here. We
1032 -- have to pop the conditional context.
1034 declare
1035 First, Last : Natural;
1037 begin
1038 Find_Token (Source => Line (Endsetclear'Length + 1
1039 .. Line'Length),
1040 Set => Letter_Set,
1041 Test => Inside,
1042 First => First,
1043 Last => Last);
1045 if Last = 0 then
1046 Error (Source_File, "'@end' without argument");
1047 else
1048 if Line (First .. Last) = "ifset" then
1049 Have_Conditional := True;
1050 Cond := Set;
1051 elsif Line (First .. Last) = "ifclear" then
1052 Have_Conditional := True;
1053 Cond := Clear;
1054 end if;
1056 if Have_Conditional then
1057 Pop_Conditional (Cond);
1058 end if;
1060 -- We fall through to the ordinary case for other @end
1061 -- directives.
1063 end if; -- @end without argument
1064 end;
1065 end if; -- Have_Conditional
1067 if (not Have_Conditional) or (Flag in Edition_Type) then
1069 -- The ordinary case
1071 if not Currently_Excluding then
1072 Put_Line (Output_File, Rewritten);
1073 end if;
1074 end if;
1075 end;
1076 end loop;
1078 Check_No_Pending_Conditional;
1079 end Process_Source_File;
1081 ---------------------------
1082 -- Initialize_Extensions --
1083 ---------------------------
1085 procedure Initialize_Extensions is
1087 procedure Add (Extension : String);
1088 -- Adds an extension which is replaced with itself (in upper
1089 -- case).
1091 procedure Add (Extension, Replacement : String);
1092 -- Adds an extension with a custom replacement
1094 ---------
1095 -- Add --
1096 ---------
1098 procedure Add (Extension : String) is
1099 begin
1100 Add (Extension, Translate (Extension, Upper_Case_Map));
1101 end Add;
1103 procedure Add (Extension, Replacement : String) is
1104 begin
1105 Set (Extensions, Extension, V (Replacement));
1106 end Add;
1108 -- Start of processing for Initialize_Extensions
1110 begin
1111 -- To avoid performance degradation, increase the constant in the
1112 -- definition of Extensions above if you add more extensions here.
1114 Add ("o", "OBJ");
1115 Add ("ads");
1116 Add ("adb");
1117 Add ("ali");
1118 Add ("ada");
1119 Add ("atb");
1120 Add ("ats");
1121 Add ("adc");
1122 Add ("c");
1123 end Initialize_Extensions;
1125 ------------------
1126 -- Is_Extension --
1127 ------------------
1129 function Is_Extension (Extension : String) return Boolean is
1130 begin
1131 return Present (Extensions, Extension);
1132 end Is_Extension;
1134 -------------------------------
1135 -- Get_Replacement_Extension --
1136 -------------------------------
1138 function Get_Replacement_Extension (Extension : String) return String is
1139 begin
1140 return S (Get (Extensions, Extension));
1141 end Get_Replacement_Extension;
1143 -------------------
1144 -- Is_Known_Word --
1145 -------------------
1147 function Is_Known_Word (Word : String) return Boolean is
1148 begin
1149 return Present (Ug_Words, Word);
1150 end Is_Known_Word;
1152 --------------------------
1153 -- Get_Replacement_Word --
1154 --------------------------
1156 function Get_Replacement_Word (Word : String) return String is
1157 begin
1158 return S (Get (Ug_Words, Word));
1159 end Get_Replacement_Word;
1161 ----------------------
1162 -- Push_Conditional --
1163 ----------------------
1165 procedure Push_Conditional (Cond : Conditional; Flag : Target_Type) is
1166 Will_Exclude : Boolean;
1168 begin
1169 -- If we are already in an excluding context, inherit this property,
1170 -- otherwise calculate it from scratch.
1172 if Conditional_TOS > 0
1173 and then Conditional_Stack (Conditional_TOS).Excluding
1174 then
1175 Will_Exclude := True;
1176 else
1177 case Cond is
1178 when Set =>
1179 Will_Exclude := Flag /= Target;
1180 when Clear =>
1181 Will_Exclude := Flag = Target;
1182 end case;
1183 end if;
1185 -- Check if the current directive is pointless because of a previous,
1186 -- enclosing directive.
1188 for J in 1 .. Conditional_TOS loop
1189 if Conditional_Stack (J).Flag = Flag then
1190 Warning (Source_File, "directive without effect because of line"
1191 & Integer'Image (Conditional_Stack (J).Starting_Line));
1192 end if;
1193 end loop;
1195 Conditional_TOS := Conditional_TOS + 1;
1196 Conditional_Stack (Conditional_TOS) :=
1197 (Starting_Line => Source_File.Line,
1198 Cond => Cond,
1199 Flag => Flag,
1200 Excluding => Will_Exclude);
1201 end Push_Conditional;
1203 ---------------------
1204 -- Pop_Conditional --
1205 ---------------------
1207 procedure Pop_Conditional (Cond : Conditional) is
1208 begin
1209 if Conditional_TOS > 0 then
1210 case Cond is
1211 when Set =>
1212 if Conditional_Stack (Conditional_TOS).Cond /= Set then
1213 Error (Source_File,
1214 "'@end ifset' does not match '@ifclear' at line"
1215 & Integer'Image (Conditional_Stack
1216 (Conditional_TOS).Starting_Line));
1217 end if;
1219 when Clear =>
1220 if Conditional_Stack (Conditional_TOS).Cond /= Clear then
1221 Error (Source_File,
1222 "'@end ifclear' does not match '@ifset' at line"
1223 & Integer'Image (Conditional_Stack
1224 (Conditional_TOS).Starting_Line));
1225 end if;
1226 end case;
1228 Conditional_TOS := Conditional_TOS - 1;
1230 else
1231 case Cond is
1232 when Set =>
1233 Error (Source_File,
1234 "'@end ifset' without corresponding '@ifset'");
1236 when Clear =>
1237 Error (Source_File,
1238 "'@end ifclear' without corresponding '@ifclear'");
1239 end case;
1240 end if;
1241 end Pop_Conditional;
1243 -------------------------
1244 -- Currently_Excluding --
1245 -------------------------
1247 function Currently_Excluding return Boolean is
1248 begin
1249 return Conditional_TOS > 0
1250 and then Conditional_Stack (Conditional_TOS).Excluding;
1251 end Currently_Excluding;
1253 ----------------------------
1254 -- VMS_Context_Determined --
1255 ----------------------------
1257 function VMS_Context_Determined return Boolean is
1258 begin
1259 for J in 1 .. Conditional_TOS loop
1260 if Conditional_Stack (J).Flag = VMS then
1261 return True;
1262 end if;
1263 end loop;
1265 return False;
1266 end VMS_Context_Determined;
1268 --------------------
1269 -- In_VMS_Section --
1270 --------------------
1272 function In_VMS_Section return Boolean is
1273 begin
1274 for J in 1 .. Conditional_TOS loop
1275 if Conditional_Stack (J).Flag = VMS then
1276 return Conditional_Stack (J).Cond = Set;
1277 end if;
1278 end loop;
1280 return False;
1281 end In_VMS_Section;
1283 ----------------------------------
1284 -- Check_No_Pending_Conditional --
1285 ----------------------------------
1287 procedure Check_No_Pending_Conditional is
1288 begin
1289 for J in 1 .. Conditional_TOS loop
1290 case Conditional_Stack (J).Cond is
1291 when Set =>
1292 Error (Source_File, "Missing '@end ifset' for '@ifset' at line"
1293 & Integer'Image (Conditional_Stack (J).Starting_Line));
1295 when Clear =>
1296 Error (Source_File,
1297 "Missing '@end ifclear' for '@ifclear' at line"
1298 & Integer'Image (Conditional_Stack (J).Starting_Line));
1299 end case;
1300 end loop;
1301 end Check_No_Pending_Conditional;
1303 -- Start of processing for Xgnatugn
1305 Valid_Command_Line : Boolean;
1306 Output_File_Name : VString;
1308 begin
1309 Initialize_Extensions;
1310 Valid_Command_Line := Argument_Count in 3 .. 5;
1312 -- First argument: Target
1314 if Valid_Command_Line then
1315 begin
1316 Target := Flag_Type'Value (Argument (1));
1318 if not Target'Valid then
1319 Valid_Command_Line := False;
1320 end if;
1322 exception
1323 when Constraint_Error =>
1324 Valid_Command_Line := False;
1325 end;
1326 end if;
1328 -- Second argument: Source_File
1330 if Valid_Command_Line then
1331 begin
1332 Source_File.Name := V (Argument (2));
1333 Open (Source_File.Data, In_File, Argument (2));
1335 exception
1336 when Ada.Text_IO.Name_Error =>
1337 Valid_Command_Line := False;
1338 end;
1339 end if;
1341 -- Third argument: Dictionary_File
1343 if Valid_Command_Line then
1344 begin
1345 Dictionary_File.Name := V (Argument (3));
1346 Open (Dictionary_File.Data, In_File, Argument (3));
1348 exception
1349 when Ada.Text_IO.Name_Error =>
1350 Valid_Command_Line := False;
1351 end;
1352 end if;
1354 -- Fourth argument: Output_File
1356 if Valid_Command_Line then
1357 if Argument_Count in 4 .. 5 then
1358 Output_File_Name := V (Argument (4));
1359 else
1360 case Target is
1361 when UNW =>
1362 Output_File_Name := V ("gnat_ugn_unw.texi");
1363 when VMS =>
1364 Output_File_Name := V ("gnat_ugn_vms.texi");
1365 end case;
1366 end if;
1368 Warnings_Enabled := Argument_Count = 5;
1370 begin
1371 Create (Output_File, Out_File, S (Output_File_Name));
1373 exception
1374 when Ada.Text_IO.Name_Error | Ada.Text_IO.Use_Error =>
1375 Valid_Command_Line := False;
1376 end;
1377 end if;
1379 if not Valid_Command_Line then
1380 Usage;
1381 Set_Exit_Status (Failure);
1383 else
1384 Read_Dictionary_File;
1385 Close (Dictionary_File.Data);
1387 -- Main processing starts here
1389 Process_Source_File;
1390 Close (Output_File);
1391 Close (Source_File.Data);
1393 New_Line (Standard_Error);
1395 if Number_Of_Warnings = 0 then
1396 Put_Line (Standard_Error, " NO Warnings");
1398 else
1399 Put (Standard_Error, Integer'Image (Number_Of_Warnings));
1400 Put (Standard_Error, " Warning");
1402 if Number_Of_Warnings > 1 then
1403 Put (Standard_Error, "s");
1404 end if;
1406 New_Line (Standard_Error);
1407 end if;
1409 if Number_Of_Errors = 0 then
1410 Put_Line (Standard_Error, " NO Errors");
1412 else
1413 Put (Standard_Error, Integer'Image (Number_Of_Errors));
1414 Put (Standard_Error, " Error");
1416 if Number_Of_Errors > 1 then
1417 Put (Standard_Error, "s");
1418 end if;
1420 New_Line (Standard_Error);
1421 end if;
1423 if Number_Of_Errors /= 0 then
1424 Set_Exit_Status (Failure);
1425 else
1426 Set_Exit_Status (Success);
1427 end if;
1428 end if;
1429 end Xgnatugn;