------------------------------------------------------------------------------ -- -- -- GNAT SYSTEM UTILITIES -- -- -- -- X G N A T U G N -- -- -- -- B o d y -- -- -- -- Copyright (C) 2003-2005, Free Software Foundation, Inc. -- -- -- -- GNAT is free software; you can redistribute it and/or modify it under -- -- terms of the GNU General Public License as published by the Free Soft- -- -- ware Foundation; either version 2, or (at your option) any later ver- -- -- sion. GNAT is distributed in the hope that it will be useful, but WITH- -- -- OUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY -- -- or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License -- -- for more details. You should have received a copy of the GNU General -- -- Public License distributed with GNAT; see file COPYING. If not, write -- -- to the Free Software Foundation, 51 Franklin Street, Fifth Floor, -- -- Boston, MA 02110-1301, USA. -- -- -- ------------------------------------------------------------------------------ -- This utility is used to process the source of gnat_ugn.texi to make a -- version suitable for running through standard Texinfo processor. It is -- invoked as follows: -- xgnatugn [ [ ] ] -- 1. is the target type of the manual, which is one of: -- unw Unix and Windows platforms -- vms OpenVMS -- 2. is the file name of the Texinfo file to be -- preprocessed. -- 3. is the name of the word list file. This file is used for -- rewriting the VMS edition. Each line contains a word mapping: The source -- word in the first column, the target word in the second column. The -- columns are separated by a '^' character. When preprocessing for VMS, the -- first word is replaced with the second. (Words consist of letters, -- digits, and the four characters "?-_~". A sequence of multiple words can -- be replaced if they are listed in the first column, separated by a single -- space character. If multiple words are to be replaced, there must be a -- replacement for each prefix.) -- 4. (optional) is the name of the output file. It defaults to -- gnat_ugn_unw.texi or gnat_ugn_vms.texi, depending on the target. -- 5. (optional, and allowed only if is explicit) -- can be any string. If present, it indicates that warning messages are -- to be output to Standard_Error. If absent, no warning messages are -- generated. -- The following steps are performed: -- In VMS mode -- Any occurrences of ^alpha^beta^ are replaced by beta. The sequence -- must fit on a single line, and there can only be one occurrence on a -- line. -- Any occurrences of a word in the Ug_Words list are replaced by the -- appropriate vms equivalents. Note that replacements do not occur -- within ^alpha^beta^ sequences. -- Any occurence of [filename].extension, where extension one of the -- following: -- "o", "ads", "adb", "ali", "ada", "atb", "ats", "adc", "c" -- replaced by the appropriate VMS names (all upper case with .o -- replaced .OBJ). Note that replacements do not occur within -- ^alpha^beta^ sequences. -- In UNW mode -- Any occurrences of ^alpha^beta^ are replaced by alpha. The sequence -- must fit on a single line. -- In both modes -- The sequence ^^^ is replaced by a single ^. This escape sequence -- must be used if the literal character ^ is to appear in the -- output. A line containing this escape sequence may not also contain -- a ^alpha^beta^ sequence. -- Process @ifset and @ifclear for the target flags (unw, vms); -- this is because we have menu problems if we let makeinfo handle -- these ifset/ifclear pairs. -- Note: @ifset/@ifclear commands for the edition flags (FSFEDITION, -- PROEDITION, ACADEMICEDITION) are passed through unchanged with Ada.Command_Line; use Ada.Command_Line; with Ada.Strings; use Ada.Strings; with Ada.Strings.Fixed; use Ada.Strings.Fixed; with Ada.Strings.Unbounded; use Ada.Strings.Unbounded; with Ada.Strings.Maps; use Ada.Strings.Maps; with Ada.Strings.Maps.Constants; use Ada.Strings.Maps.Constants; with Ada.Text_IO; use Ada.Text_IO; with GNAT.Spitbol; use GNAT.Spitbol; with GNAT.Spitbol.Table_VString; use GNAT.Spitbol.Table_VString; procedure Xgnatugn is procedure Usage; -- Print usage information. Invoked if an invalid command line is -- encountered. Output_File : File_Type; -- The preprocessed output is written to this file type Input_File is record Name : VString; Data : File_Type; Line : Natural := 0; end record; -- Records information on an input file. Name and Line are used -- in error messages, Line is updated automatically by Get_Line. function Get_Line (Input : access Input_File) return String; -- Returns a line from Input and performs the necessary -- line-oriented checks (length, character set, trailing spaces). Number_Of_Warnings : Natural := 0; Number_Of_Errors : Natural := 0; Warnings_Enabled : Boolean; procedure Error (Input : Input_File; At_Character : Natural; Message : String); procedure Error (Input : Input_File; Message : String); -- Prints a message reporting an error on line Input.Line. If -- At_Character is not 0, indicate the exact character at which -- the error occurs. procedure Warning (Input : Input_File; At_Character : Natural; Message : String); procedure Warning (Input : Input_File; Message : String); -- Like Error, but just print a warning message Dictionary_File : aliased Input_File; procedure Read_Dictionary_File; -- Dictionary_File is opened using the name given on the command -- line. It contains the replacements for the Ug_Words list. -- Read_Dictionary_File reads Dictionary_File and fills the -- Ug_Words table. Source_File : aliased Input_File; procedure Process_Source_File; -- Source_File is opened using the name given on the command line. -- It contains the Texinfo source code. Process_Source_File -- performs the necessary replacements. type Flag_Type is (UNW, VMS, FSFEDITION, PROEDITION, ACADEMICEDITION); -- The flags permitted in @ifset or @ifclear commands: -- -- Targets for preprocessing -- UNW (Unix and Windows) or VMS -- -- Editions of the manual -- FSFEDITION, PROEDITION, or ACADEMICEDITION -- -- Conditional commands for target are processed by xgnatugn -- -- Conditional commands for edition are passed through unchanged subtype Target_Type is Flag_Type range UNW .. VMS; subtype Edition_Type is Flag_Type range FSFEDITION .. ACADEMICEDITION; Target : Target_Type; -- The Target variable is initialized using the command line Valid_Characters : constant Character_Set := To_Set (Span => (' ', '~')); -- This array controls which characters are permitted in the input -- file (after line breaks have been removed). Valid characters -- are all printable ASCII characters and the space character. Word_Characters : constant Character_Set := (To_Set (Ranges => (('0', '9'), ('a', 'z'), ('A', 'Z'))) or To_Set ("?-_~")); -- The characters which are permitted in words. Other (valid) -- characters are assumed to be delimiters between words. Note that -- this set has to include all characters of the source words of the -- Ug_Words dictionary. Reject_Trailing_Spaces : constant Boolean := True; -- Controls whether Xgnatug rejects superfluous space characters -- at the end of lines. Maximum_Line_Length : constant Positive := 79; Fatal_Line_Length_Limit : constant Positive := 5000; Fatal_Line_Length : exception; -- If Maximum_Line_Length is exceeded in an input file, an error -- message is printed. If Fatal_Line_Length is exceeded, -- execution terminates with a Fatal_Line_Length exception. VMS_Escape_Character : constant Character := '^'; -- The character used to mark VMS alternatives (^alpha^beta^) Extensions : GNAT.Spitbol.Table_VString.Table (20); procedure Initialize_Extensions; -- This table records extensions and their replacement for -- rewriting filenames in the VMS version of the manual. function Is_Extension (Extension : String) return Boolean; function Get_Replacement_Extension (Extension : String) return String; -- These functions query the replacement table. Is_Extension -- checks if the given string is a known extension. -- Get_Replacement returns the replacement extension. Ug_Words : GNAT.Spitbol.Table_VString.Table (200); function Is_Known_Word (Word : String) return Boolean; function Get_Replacement_Word (Word : String) return String; -- The Ug_Words table lists replacement words for the VMS version -- of the manual. Is_Known_Word and Get_Replacement_Word query -- this table. The table is filled using Read_Dictionary_File. function Rewrite_Source_Line (Line : String) return String; -- This subprogram takes a line and rewrites it according to Target. -- It relies on information in Source_File to generate error messages. type Conditional is (Set, Clear); procedure Push_Conditional (Cond : Conditional; Flag : Target_Type); procedure Pop_Conditional (Cond : Conditional); -- These subprograms deal with conditional processing (@ifset/@ifclear). -- They rely on information in Source_File to generate error messages. function Currently_Excluding return Boolean; -- Returns true if conditional processing directives imply that the -- current line should not be included in the output. function VMS_Context_Determined return Boolean; -- Returns true if, in the current conditional preprocessing context, we -- always have a VMS or a non-VMS version, regardless of the value of -- Target. function In_VMS_Section return Boolean; -- Returns True if in an "@ifset vms" section procedure Check_No_Pending_Conditional; -- Checks that all preprocessing directives have been properly matched by -- their @end counterpart. If this is not the case, print an error -- message. -- The following definitions implement a stack to track the conditional -- preprocessing context. type Conditional_Context is record Starting_Line : Positive; Cond : Conditional; Flag : Flag_Type; Excluding : Boolean; end record; Conditional_Stack_Depth : constant := 3; Conditional_Stack : array (1 .. Conditional_Stack_Depth) of Conditional_Context; Conditional_TOS : Natural := 0; -- Pointer to the Top Of Stack for Conditional_Stack ----------- -- Usage -- ----------- procedure Usage is begin Put_Line (Standard_Error, "usage: xgnatugn TARGET SOURCE DICTIONARY [OUTFILE [WARNINGS]]"); New_Line; Put_Line (Standard_Error, "TARGET is one of:"); for T in Target_Type'Range loop Put_Line (Standard_Error, " " & Target_Type'Image (T)); end loop; New_Line; Put_Line (Standard_Error, "SOURCE is the source file to process."); New_Line; Put_Line (Standard_Error, "DICTIONARY is the name of a file " & "that contains word replacements"); Put_Line (Standard_Error, "for the VMS version."); New_Line; Put_Line (Standard_Error, "OUT-FILE, if present, is the output file to be created;"); Put_Line (Standard_Error, "If OUT-FILE is absent, the output file is either " & "gnat_ugn_unw.texi, "); Put_Line (Standard_Error, "or gnat_ugn_vms.texi, depending on TARGET."); New_Line; Put_Line (Standard_Error, "WARNINGS, if present, is any string;"); Put_Line (Standard_Error, "it will result in warning messages (e.g., line too long))"); Put_Line (Standard_Error, "being output to Standard_Error."); end Usage; -------------- -- Get_Line -- -------------- function Get_Line (Input : access Input_File) return String is Line_Buffer : String (1 .. Fatal_Line_Length_Limit); Last : Natural; begin Input.Line := Input.Line + 1; Get_Line (Input.Data, Line_Buffer, Last); if Last = Line_Buffer'Last then Error (Input.all, "line exceeds fatal line length limit"); raise Fatal_Line_Length; end if; declare Line : String renames Line_Buffer (Line_Buffer'First .. Last); begin for J in Line'Range loop if not Is_In (Line (J), Valid_Characters) then Error (Input.all, J, "invalid character"); exit; end if; end loop; if Line'Length > Maximum_Line_Length then Warning (Input.all, Maximum_Line_Length + 1, "line too long"); end if; if Reject_Trailing_Spaces and then Line'Length > 0 and then Line (Line'Last) = ' ' then Error (Input.all, Line'Last, "trailing space character"); end if; return Trim (Line, Right); end; end Get_Line; ----------- -- Error -- ----------- procedure Error (Input : Input_File; Message : String) is begin Error (Input, 0, Message); end Error; procedure Error (Input : Input_File; At_Character : Natural; Message : String) is Line_Image : constant String := Integer'Image (Input.Line); At_Character_Image : constant String := Integer'Image (At_Character); -- These variables are required because we have to drop the leading -- space character. begin Number_Of_Errors := Number_Of_Errors + 1; if At_Character > 0 then Put_Line (Standard_Error, S (Input.Name) & ':' & Line_Image (Line_Image'First + 1 .. Line_Image'Last) & ':' & At_Character_Image (At_Character_Image'First + 1 .. At_Character_Image'Last) & ": " & Message); else Put_Line (Standard_Error, S (Input.Name) & ':' & Line_Image (Line_Image'First + 1 .. Line_Image'Last) & ": " & Message); end if; end Error; ------------- -- Warning -- ------------- procedure Warning (Input : Input_File; Message : String) is begin if Warnings_Enabled then Warning (Input, 0, Message); end if; end Warning; procedure Warning (Input : Input_File; At_Character : Natural; Message : String) is Line_Image : constant String := Integer'Image (Input.Line); At_Character_Image : constant String := Integer'Image (At_Character); -- These variables are required because we have to drop the leading -- space character. begin if not Warnings_Enabled then return; end if; Number_Of_Warnings := Number_Of_Warnings + 1; if At_Character > 0 then Put_Line (Standard_Error, S (Input.Name) & ':' & Line_Image (Line_Image'First + 1 .. Line_Image'Last) & ':' & At_Character_Image (At_Character_Image'First + 1 .. At_Character_Image'Last) & ": warning: " & Message); else Put_Line (Standard_Error, S (Input.Name) & ':' & Line_Image (Line_Image'First + 1 .. Line_Image'Last) & ": warning: " & Message); end if; end Warning; -------------------------- -- Read_Dictionary_File -- -------------------------- procedure Read_Dictionary_File is begin while not End_Of_File (Dictionary_File.Data) loop declare Line : constant String := Get_Line (Dictionary_File'Access); Split : constant Natural := Index (Line, (1 => VMS_Escape_Character)); begin if Line'Length = 0 then Error (Dictionary_File, "empty line in dictionary file"); elsif Line (Line'First) = ' ' then Error (Dictionary_File, 1, "line starts with space character"); elsif Split = 0 then Error (Dictionary_File, "line does not contain " & VMS_Escape_Character & " character"); else declare Source : constant String := Trim (Line (1 .. Split - 1), Both); Target : constant String := Trim (Line (Split + 1 .. Line'Last), Both); Two_Spaces : constant Natural := Index (Source, " "); Non_Word_Character : constant Natural := Index (Source, Word_Characters or To_Set (" "), Outside); begin if Two_Spaces /= 0 then Error (Dictionary_File, Two_Spaces, "multiple space characters in source word"); end if; if Non_Word_Character /= 0 then Error (Dictionary_File, Non_Word_Character, "illegal character in source word"); end if; if Source'Length = 0 then Error (Dictionary_File, "source is empty"); elsif Target'Length = 0 then Error (Dictionary_File, "target is empty"); else Set (Ug_Words, Source, V (Target)); -- Ensure that if Source is a sequence of words -- "WORD1 WORD2 ...", we already have a mapping for -- "WORD1". for J in Source'Range loop if Source (J) = ' ' then declare Prefix : String renames Source (Source'First .. J - 1); begin if not Is_Known_Word (Prefix) then Error (Dictionary_File, "prefix '" & Prefix & "' not known at this point"); end if; end; end if; end loop; end if; end; end if; end; end loop; end Read_Dictionary_File; ------------------------- -- Rewrite_Source_Line -- ------------------------- function Rewrite_Source_Line (Line : String) return String is -- We use a simple lexer to split the line into tokens: -- Word consisting entirely of Word_Characters -- VMS_Alternative ^alpha^beta^ replacement (but not ^^^) -- Space a space character -- Other everything else (sequence of non-word characters) -- VMS_Error incomplete VMS alternative -- End_Of_Line no more characters on this line -- A sequence of three VMS_Escape_Characters is automatically -- collapsed to an Other token. type Token_Span is record First, Last : Positive; end record; -- The character range covered by a token in Line type Token_Kind is (End_Of_Line, Word, Other, VMS_Alternative, VMS_Error); type Token_Record (Kind : Token_Kind := End_Of_Line) is record First : Positive; case Kind is when Word | Other => Span : Token_Span; when VMS_Alternative => Non_VMS, VMS : Token_Span; when VMS_Error | End_Of_Line => null; end case; end record; Input_Position : Positive := Line'First; Token : Token_Record; -- The position of the next character to be processed by Next_Token procedure Next_Token; -- Returns the next token in Line, starting at Input_Position Rewritten_Line : VString; -- Collects the line as it is rewritten procedure Rewrite_Word; -- The current token is assumed to be a Word. When processing the VMS -- version of the manual, additional tokens are gathered to check if -- we have a file name or a sequence of known words. procedure Maybe_Rewrite_Extension; -- The current token is assumed to be Other. When processing the VMS -- version of the manual and the token represents a single dot ".", -- the following word is rewritten according to the rules for -- extensions. VMS_Token_Seen : Boolean := False; -- This is set to true if a VMS_Alternative has been encountered, or a -- ^^^ token. ---------------- -- Next_Token -- ---------------- procedure Next_Token is Remaining_Line : String renames Line (Input_Position .. Line'Last); Last_Character : Natural; begin if Remaining_Line'Length = 0 then Token := (End_Of_Line, Remaining_Line'First); return; end if; -- ^alpha^beta^, the VMS_Alternative case if Remaining_Line (Remaining_Line'First) = VMS_Escape_Character then declare VMS_Second_Character, VMS_Third_Character : Natural; begin if VMS_Token_Seen then Error (Source_File, Remaining_Line'First, "multiple " & VMS_Escape_Character & " characters on a single line"); else VMS_Token_Seen := True; end if; -- Find the second and third escape character. If one of -- them is not present, generate an error token. VMS_Second_Character := Index (Remaining_Line (Remaining_Line'First + 1 .. Remaining_Line'Last), (1 => VMS_Escape_Character)); if VMS_Second_Character = 0 then Input_Position := Remaining_Line'Last + 1; Token := (VMS_Error, Remaining_Line'First); return; end if; VMS_Third_Character := Index (Remaining_Line (VMS_Second_Character + 1 .. Remaining_Line'Last), (1 => VMS_Escape_Character)); if VMS_Third_Character = 0 then Input_Position := Remaining_Line'Last + 1; Token := (VMS_Error, Remaining_Line'First); return; end if; -- Consume all the characters we are about to include in -- the token. Input_Position := VMS_Third_Character + 1; -- Check if we are in a ^^^ situation, and return an Other -- token in this case. if Remaining_Line'First + 1 = VMS_Second_Character and then Remaining_Line'First + 2 = VMS_Third_Character then Token := (Other, Remaining_Line'First, (Remaining_Line'First, Remaining_Line'First)); return; end if; Token := (VMS_Alternative, Remaining_Line'First, (Remaining_Line'First + 1, VMS_Second_Character - 1), (VMS_Second_Character + 1, VMS_Third_Character - 1)); return; end; end if; -- VMS_Alternative -- The Word case. Search for characters not in Word_Characters. -- We have found a word if the first non-word character is not -- the first character in Remaining_Line, i.e. if Remaining_Line -- starts with a word character. Last_Character := Index (Remaining_Line, Word_Characters, Outside); if Last_Character /= Remaining_Line'First then -- If we haven't found a character which is not in -- Word_Characters, all remaining characters are part of the -- current Word token. if Last_Character = 0 then Last_Character := Remaining_Line'Last + 1; end if; Input_Position := Last_Character; Token := (Word, Remaining_Line'First, (Remaining_Line'First, Last_Character - 1)); return; end if; -- Remaining characters are in the Other category. To speed -- up processing, we collect them together if there are several -- of them. Input_Position := Last_Character + 1; Token := (Other, Remaining_Line'First, (Remaining_Line'First, Last_Character)); end Next_Token; ------------------ -- Rewrite_Word -- ------------------ procedure Rewrite_Word is First_Word : String renames Line (Token.Span.First .. Token.Span.Last); begin -- We do not perform any error checking below, so we can just skip -- all processing for the non-VMS version. if Target /= VMS then Append (Rewritten_Line, First_Word); Next_Token; return; end if; if Is_Known_Word (First_Word) then -- If we have a word from the dictionary, we look for the -- longest possible sequence we can rewrite. declare Seq : Token_Span := Token.Span; Lost_Space : Boolean := False; begin Next_Token; loop if Token.Kind = Other and then Line (Token.Span.First .. Token.Span.Last) = " " then Next_Token; if Token.Kind /= Word or else not Is_Known_Word (Line (Seq.First .. Token.Span.Last)) then -- When we reach this point, the following -- conditions are true: -- -- Seq is a known word. -- The previous token was a space character. -- Seq extended to the current token is not a -- known word. Lost_Space := True; exit; else -- Extend Seq to cover the current (known) word Seq.Last := Token.Span.Last; Next_Token; end if; else -- When we reach this point, the following conditions -- are true: -- -- Seq is a known word. -- The previous token was a word. -- The current token is not a space character. exit; end if; end loop; -- Rewrite Seq, and add the lost space if necessary Append (Rewritten_Line, Get_Replacement_Word (Line (Seq.First .. Seq.Last))); if Lost_Space then Append (Rewritten_Line, ' '); end if; -- The unknown token will be processed during the -- next iteration of the main loop. return; end; end if; Next_Token; if Token.Kind = Other and then Line (Token.Span.First .. Token.Span.Last) = "." then -- Deal with extensions Next_Token; if Token.Kind = Word and then Is_Extension (Line (Token.Span.First .. Token.Span.Last)) then -- We have discovered a file extension. Convert the file -- name to upper case. Append (Rewritten_Line, Translate (First_Word, Upper_Case_Map) & '.'); Append (Rewritten_Line, Get_Replacement_Extension (Line (Token.Span.First .. Token.Span.Last))); Next_Token; else -- We already have: Word ".", followed by an unknown token Append (Rewritten_Line, First_Word & '.'); -- The unknown token will be processed during the next -- iteration of the main loop. end if; else -- We have an unknown Word, followed by an unknown token. -- The unknown token will be processed by the outer loop. Append (Rewritten_Line, First_Word); end if; end Rewrite_Word; ----------------------------- -- Maybe_Rewrite_Extension -- ----------------------------- procedure Maybe_Rewrite_Extension is begin -- Again, we need no special processing in the non-VMS case if Target = VMS and then Line (Token.Span.First .. Token.Span.Last) = "." then -- This extension is not preceded by a word, otherwise -- Rewrite_Word would have handled it. Next_Token; if Token.Kind = Word and then Is_Extension (Line (Token.Span.First .. Token.Span.Last)) then Append (Rewritten_Line, '.' & Get_Replacement_Extension (Line (Token.Span.First .. Token.Span.Last))); Next_Token; else Append (Rewritten_Line, '.'); end if; else Append (Rewritten_Line, Line (Token.Span.First .. Token.Span.Last)); Next_Token; end if; end Maybe_Rewrite_Extension; -- Start of processing for Process_Source_Line begin -- The following parser recognizes the following special token -- sequences: -- Word "." Word rewrite as file name if second word is extension -- Word " " Word rewrite as a single word using Ug_Words table Next_Token; loop case Token.Kind is when End_Of_Line => exit; when Word => Rewrite_Word; when Other => Maybe_Rewrite_Extension; when VMS_Alternative => if VMS_Context_Determined then if (not In_VMS_Section) or else Line (Token.VMS.First .. Token.VMS.Last) /= Line (Token.Non_VMS.First .. Token.Non_VMS.Last) then Warning (Source_File, Token.First, "VMS alternative already determined " & "by conditionals"); end if; end if; if Target = VMS then Append (Rewritten_Line, Line (Token.VMS.First .. Token.VMS.Last)); else Append (Rewritten_Line, Line (Token.Non_VMS.First .. Token.Non_VMS.Last)); end if; Next_Token; when VMS_Error => Error (Source_File, Token.First, "invalid VMS alternative"); Next_Token; end case; end loop; return S (Rewritten_Line); end Rewrite_Source_Line; ------------------------- -- Process_Source_File -- ------------------------- procedure Process_Source_File is Ifset : constant String := "@ifset "; Ifclear : constant String := "@ifclear "; Endsetclear : constant String := "@end "; -- Strings to be recognized for conditional processing begin while not End_Of_File (Source_File.Data) loop declare Line : constant String := Get_Line (Source_File'Access); Rewritten : constant String := Rewrite_Source_Line (Line); -- We unconditionally rewrite the line so that we can check the -- syntax of all lines, and not only those which are actually -- included in the output. Have_Conditional : Boolean := False; -- True if we have encountered a conditional preprocessing -- directive. Cond : Conditional; -- The kind of the directive Flag : Flag_Type; -- Its flag begin -- If the line starts with @ifset or @ifclear, we try to convert -- the following flag to one of our flag types. If we fail, -- Have_Conditional remains False. if Line'Length >= Ifset'Length and then Line (1 .. Ifset'Length) = Ifset then Cond := Set; declare Arg : constant String := Trim (Line (Ifset'Length + 1 .. Line'Last), Both); begin Flag := Flag_Type'Value (Arg); Have_Conditional := True; case Flag is when Target_Type => if Translate (Target_Type'Image (Flag), Lower_Case_Map) /= Arg then Error (Source_File, "flag has to be lowercase"); end if; when Edition_Type => null; end case; exception when Constraint_Error => Error (Source_File, "unknown flag for '@ifset'"); end; elsif Line'Length >= Ifclear'Length and then Line (1 .. Ifclear'Length) = Ifclear then Cond := Clear; declare Arg : constant String := Trim (Line (Ifclear'Length + 1 .. Line'Last), Both); begin Flag := Flag_Type'Value (Arg); Have_Conditional := True; case Flag is when Target_Type => if Translate (Target_Type'Image (Flag), Lower_Case_Map) /= Arg then Error (Source_File, "flag has to be lowercase"); end if; when Edition_Type => null; end case; exception when Constraint_Error => Error (Source_File, "unknown flag for '@ifclear'"); end; end if; if Have_Conditional and (Flag in Target_Type) then -- We create a new conditional context and suppress the -- directive in the output. Push_Conditional (Cond, Flag); elsif Line'Length >= Endsetclear'Length and then Line (1 .. Endsetclear'Length) = Endsetclear and then (Flag in Target_Type) then -- The '@end ifset'/'@end ifclear' case is handled here. We -- have to pop the conditional context. declare First, Last : Natural; begin Find_Token (Source => Line (Endsetclear'Length + 1 .. Line'Length), Set => Letter_Set, Test => Inside, First => First, Last => Last); if Last = 0 then Error (Source_File, "'@end' without argument"); else if Line (First .. Last) = "ifset" then Have_Conditional := True; Cond := Set; elsif Line (First .. Last) = "ifclear" then Have_Conditional := True; Cond := Clear; end if; if Have_Conditional then Pop_Conditional (Cond); end if; -- We fall through to the ordinary case for other @end -- directives. end if; -- @end without argument end; end if; -- Have_Conditional if (not Have_Conditional) or (Flag in Edition_Type) then -- The ordinary case if not Currently_Excluding then Put_Line (Output_File, Rewritten); end if; end if; end; end loop; Check_No_Pending_Conditional; end Process_Source_File; --------------------------- -- Initialize_Extensions -- --------------------------- procedure Initialize_Extensions is procedure Add (Extension : String); -- Adds an extension which is replaced with itself (in upper -- case). procedure Add (Extension, Replacement : String); -- Adds an extension with a custom replacement --------- -- Add -- --------- procedure Add (Extension : String) is begin Add (Extension, Translate (Extension, Upper_Case_Map)); end Add; procedure Add (Extension, Replacement : String) is begin Set (Extensions, Extension, V (Replacement)); end Add; -- Start of processing for Initialize_Extensions begin -- To avoid performance degradation, increase the constant in the -- definition of Extensions above if you add more extensions here. Add ("o", "OBJ"); Add ("ads"); Add ("adb"); Add ("ali"); Add ("ada"); Add ("atb"); Add ("ats"); Add ("adc"); Add ("c"); end Initialize_Extensions; ------------------ -- Is_Extension -- ------------------ function Is_Extension (Extension : String) return Boolean is begin return Present (Extensions, Extension); end Is_Extension; ------------------------------- -- Get_Replacement_Extension -- ------------------------------- function Get_Replacement_Extension (Extension : String) return String is begin return S (Get (Extensions, Extension)); end Get_Replacement_Extension; ------------------- -- Is_Known_Word -- ------------------- function Is_Known_Word (Word : String) return Boolean is begin return Present (Ug_Words, Word); end Is_Known_Word; -------------------------- -- Get_Replacement_Word -- -------------------------- function Get_Replacement_Word (Word : String) return String is begin return S (Get (Ug_Words, Word)); end Get_Replacement_Word; ---------------------- -- Push_Conditional -- ---------------------- procedure Push_Conditional (Cond : Conditional; Flag : Target_Type) is Will_Exclude : Boolean; begin -- If we are already in an excluding context, inherit this property, -- otherwise calculate it from scratch. if Conditional_TOS > 0 and then Conditional_Stack (Conditional_TOS).Excluding then Will_Exclude := True; else case Cond is when Set => Will_Exclude := Flag /= Target; when Clear => Will_Exclude := Flag = Target; end case; end if; -- Check if the current directive is pointless because of a previous, -- enclosing directive. for J in 1 .. Conditional_TOS loop if Conditional_Stack (J).Flag = Flag then Warning (Source_File, "directive without effect because of line" & Integer'Image (Conditional_Stack (J).Starting_Line)); end if; end loop; Conditional_TOS := Conditional_TOS + 1; Conditional_Stack (Conditional_TOS) := (Starting_Line => Source_File.Line, Cond => Cond, Flag => Flag, Excluding => Will_Exclude); end Push_Conditional; --------------------- -- Pop_Conditional -- --------------------- procedure Pop_Conditional (Cond : Conditional) is begin if Conditional_TOS > 0 then case Cond is when Set => if Conditional_Stack (Conditional_TOS).Cond /= Set then Error (Source_File, "'@end ifset' does not match '@ifclear' at line" & Integer'Image (Conditional_Stack (Conditional_TOS).Starting_Line)); end if; when Clear => if Conditional_Stack (Conditional_TOS).Cond /= Clear then Error (Source_File, "'@end ifclear' does not match '@ifset' at line" & Integer'Image (Conditional_Stack (Conditional_TOS).Starting_Line)); end if; end case; Conditional_TOS := Conditional_TOS - 1; else case Cond is when Set => Error (Source_File, "'@end ifset' without corresponding '@ifset'"); when Clear => Error (Source_File, "'@end ifclear' without corresponding '@ifclear'"); end case; end if; end Pop_Conditional; ------------------------- -- Currently_Excluding -- ------------------------- function Currently_Excluding return Boolean is begin return Conditional_TOS > 0 and then Conditional_Stack (Conditional_TOS).Excluding; end Currently_Excluding; ---------------------------- -- VMS_Context_Determined -- ---------------------------- function VMS_Context_Determined return Boolean is begin for J in 1 .. Conditional_TOS loop if Conditional_Stack (J).Flag = VMS then return True; end if; end loop; return False; end VMS_Context_Determined; -------------------- -- In_VMS_Section -- -------------------- function In_VMS_Section return Boolean is begin for J in 1 .. Conditional_TOS loop if Conditional_Stack (J).Flag = VMS then return Conditional_Stack (J).Cond = Set; end if; end loop; return False; end In_VMS_Section; ---------------------------------- -- Check_No_Pending_Conditional -- ---------------------------------- procedure Check_No_Pending_Conditional is begin for J in 1 .. Conditional_TOS loop case Conditional_Stack (J).Cond is when Set => Error (Source_File, "Missing '@end ifset' for '@ifset' at line" & Integer'Image (Conditional_Stack (J).Starting_Line)); when Clear => Error (Source_File, "Missing '@end ifclear' for '@ifclear' at line" & Integer'Image (Conditional_Stack (J).Starting_Line)); end case; end loop; end Check_No_Pending_Conditional; -- Start of processing for Xgnatugn Valid_Command_Line : Boolean; Output_File_Name : VString; begin Initialize_Extensions; Valid_Command_Line := Argument_Count in 3 .. 5; -- First argument: Target if Valid_Command_Line then begin Target := Flag_Type'Value (Argument (1)); if not Target'Valid then Valid_Command_Line := False; end if; exception when Constraint_Error => Valid_Command_Line := False; end; end if; -- Second argument: Source_File if Valid_Command_Line then begin Source_File.Name := V (Argument (2)); Open (Source_File.Data, In_File, Argument (2)); exception when Name_Error => Valid_Command_Line := False; end; end if; -- Third argument: Dictionary_File if Valid_Command_Line then begin Dictionary_File.Name := V (Argument (3)); Open (Dictionary_File.Data, In_File, Argument (3)); exception when Name_Error => Valid_Command_Line := False; end; end if; -- Fourth argument: Output_File if Valid_Command_Line then if Argument_Count in 4 .. 5 then Output_File_Name := V (Argument (4)); else case Target is when UNW => Output_File_Name := V ("gnat_ugn_unw.texi"); when VMS => Output_File_Name := V ("gnat_ugn_vms.texi"); end case; end if; Warnings_Enabled := Argument_Count = 5; begin Create (Output_File, Out_File, S (Output_File_Name)); exception when Name_Error | Use_Error => Valid_Command_Line := False; end; end if; if not Valid_Command_Line then Usage; Set_Exit_Status (Failure); else Read_Dictionary_File; Close (Dictionary_File.Data); -- Main processing starts here Process_Source_File; Close (Output_File); Close (Source_File.Data); New_Line (Standard_Error); if Number_Of_Warnings = 0 then Put_Line (Standard_Error, " NO Warnings"); else Put (Standard_Error, Integer'Image (Number_Of_Warnings)); Put (Standard_Error, " Warning"); if Number_Of_Warnings > 1 then Put (Standard_Error, "s"); end if; New_Line (Standard_Error); end if; if Number_Of_Errors = 0 then Put_Line (Standard_Error, " NO Errors"); else Put (Standard_Error, Integer'Image (Number_Of_Errors)); Put (Standard_Error, " Error"); if Number_Of_Errors > 1 then Put (Standard_Error, "s"); end if; New_Line (Standard_Error); end if; if Number_Of_Errors /= 0 then Set_Exit_Status (Failure); else Set_Exit_Status (Success); end if; end if; end Xgnatugn;