------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                                 P R E P                                  --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 2002-2013, 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 3,  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 COPYING3.  If not, go to --
-- http://www.gnu.org/licenses for a complete copy of the license.          --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with Csets;    use Csets;
with Err_Vars; use Err_Vars;
with Opt;      use Opt;
with Osint;    use Osint;
with Output;   use Output;
with Scans;    use Scans;
with Snames;   use Snames;
with Sinput;
with Stringt;  use Stringt;
with Table;
with Uintp;    use Uintp;

with GNAT.Heap_Sort_G;

package body Prep is

   use Symbol_Table;

   type Token_Name_Array is array (Token_Type) of Name_Id;
   Token_Names : constant Token_Name_Array :=
     (Tok_Abort     => Name_Abort,
      Tok_Abs       => Name_Abs,
      Tok_Abstract  => Name_Abstract,
      Tok_Accept    => Name_Accept,
      Tok_Aliased   => Name_Aliased,
      Tok_All       => Name_All,
      Tok_Array     => Name_Array,
      Tok_And       => Name_And,
      Tok_At        => Name_At,
      Tok_Begin     => Name_Begin,
      Tok_Body      => Name_Body,
      Tok_Case      => Name_Case,
      Tok_Constant  => Name_Constant,
      Tok_Declare   => Name_Declare,
      Tok_Delay     => Name_Delay,
      Tok_Delta     => Name_Delta,
      Tok_Digits    => Name_Digits,
      Tok_Else      => Name_Else,
      Tok_Elsif     => Name_Elsif,
      Tok_End       => Name_End,
      Tok_Entry     => Name_Entry,
      Tok_Exception => Name_Exception,
      Tok_Exit      => Name_Exit,
      Tok_For       => Name_For,
      Tok_Function  => Name_Function,
      Tok_Generic   => Name_Generic,
      Tok_Goto      => Name_Goto,
      Tok_If        => Name_If,
      Tok_Is        => Name_Is,
      Tok_Limited   => Name_Limited,
      Tok_Loop      => Name_Loop,
      Tok_Mod       => Name_Mod,
      Tok_New       => Name_New,
      Tok_Null      => Name_Null,
      Tok_Of        => Name_Of,
      Tok_Or        => Name_Or,
      Tok_Others    => Name_Others,
      Tok_Out       => Name_Out,
      Tok_Package   => Name_Package,
      Tok_Pragma    => Name_Pragma,
      Tok_Private   => Name_Private,
      Tok_Procedure => Name_Procedure,
      Tok_Protected => Name_Protected,
      Tok_Raise     => Name_Raise,
      Tok_Range     => Name_Range,
      Tok_Record    => Name_Record,
      Tok_Rem       => Name_Rem,
      Tok_Renames   => Name_Renames,
      Tok_Requeue   => Name_Requeue,
      Tok_Return    => Name_Return,
      Tok_Reverse   => Name_Reverse,
      Tok_Select    => Name_Select,
      Tok_Separate  => Name_Separate,
      Tok_Subtype   => Name_Subtype,
      Tok_Tagged    => Name_Tagged,
      Tok_Task      => Name_Task,
      Tok_Terminate => Name_Terminate,
      Tok_Then      => Name_Then,
      Tok_Type      => Name_Type,
      Tok_Until     => Name_Until,
      Tok_Use       => Name_Use,
      Tok_When      => Name_When,
      Tok_While     => Name_While,
      Tok_With      => Name_With,
      Tok_Xor       => Name_Xor,
      others        => No_Name);

   Already_Initialized : Boolean := False;
   --  Used to avoid repetition of the part of the initialisation that needs
   --  to be done only once.

   Empty_String : String_Id;
   --  "", as a string_id

   String_False : String_Id;
   --  "false", as a string_id

   ---------------
   -- Behaviour --
   ---------------

   --  Accesses to procedure specified by procedure Initialize

   Error_Msg : Error_Msg_Proc;
   --  Report an error

   Scan : Scan_Proc;
   --  Scan one token

   Set_Ignore_Errors : Set_Ignore_Errors_Proc;
   --  Indicate if error should be taken into account

   Put_Char : Put_Char_Proc;
   --  Output one character

   New_EOL : New_EOL_Proc;
   --  Output an end of line indication

   -------------------------------
   -- State of the Preprocessor --
   -------------------------------

   type Pp_State is record
      If_Ptr : Source_Ptr;
      --  The location of the #if statement (used to flag #if with no
      --  corresponding #end if, at the end).

      Else_Ptr : Source_Ptr;
      --  The location of the #else statement (used to detect multiple #else's)

      Deleting : Boolean;
      --  Set to True when the code should be deleted or commented out

      Match_Seen : Boolean;
      --  Set to True when a condition in an #if or an #elsif is True. Also set
      --  to True if Deleting at the previous level is True. Used to decide if
      --  Deleting should be set to True in a following #elsif or #else.

   end record;

   type Pp_Depth is new Nat;

   Ground : constant Pp_Depth := 0;

   package Pp_States is new Table.Table
     (Table_Component_Type => Pp_State,
      Table_Index_Type     => Pp_Depth,
      Table_Low_Bound      => 1,
      Table_Initial        => 10,
      Table_Increment      => 100,
      Table_Name           => "Prep.Pp_States");
   --  A stack of the states of the preprocessor, for nested #if

   type Operator is (None, Op_Or, Op_And);

   -----------------
   -- Subprograms --
   -----------------

   function Deleting return Boolean;
   --  Return True if code should be deleted or commented out

   function Expression
     (Evaluate_It  : Boolean;
      Complemented : Boolean := False) return Boolean;
   --  Evaluate a condition in an #if or an #elsif statement. If Evaluate_It
   --  is False, the condition is effectively evaluated, otherwise, only the
   --  syntax is checked.

   procedure Go_To_End_Of_Line;
   --  Advance the scan pointer until we reach an end of line or the end of the
   --  buffer.

   function Matching_Strings (S1, S2 : String_Id) return Boolean;
   --  Returns True if the two string parameters are equal (case insensitive)

   ---------------------------------------
   -- Change_Reserved_Keyword_To_Symbol --
   ---------------------------------------

   procedure Change_Reserved_Keyword_To_Symbol
     (All_Keywords : Boolean := False)
   is
      New_Name : constant Name_Id := Token_Names (Token);

   begin
      if New_Name /= No_Name then
         case Token is
            when Tok_If  | Tok_Else | Tok_Elsif | Tok_End |
                 Tok_And | Tok_Or   | Tok_Then =>
               if All_Keywords then
                  Token := Tok_Identifier;
                  Token_Name := New_Name;
               end if;

            when others =>
               Token := Tok_Identifier;
               Token_Name := New_Name;
         end case;
      end if;
   end Change_Reserved_Keyword_To_Symbol;

   ------------------------------------------
   -- Check_Command_Line_Symbol_Definition --
   ------------------------------------------

   procedure Check_Command_Line_Symbol_Definition
     (Definition  : String;
      Data        : out Symbol_Data)
   is
      Index       : Natural := 0;
      Result      : Symbol_Data;

   begin
      --  Look for the character '='

      for J in Definition'Range loop
         if Definition (J) = '=' then
            Index := J;
            exit;
         end if;
      end loop;

      --  If no character '=', then the value is True

      if Index = 0 then

         --  Put the symbol in the name buffer

         Name_Len := Definition'Length;
         Name_Buffer (1 .. Name_Len) := Definition;
         Result := True_Value;

      elsif Index = Definition'First then
         Fail ("invalid symbol definition """ & Definition & """");

      else
         --  Put the symbol in the name buffer

         Name_Len := Index - Definition'First;
         Name_Buffer (1 .. Name_Len) :=
           String'(Definition (Definition'First .. Index - 1));

         --  Check the syntax of the value

         if Definition (Index + 1) /= '"'
           or else Definition (Definition'Last) /= '"'
         then
            for J in Index + 1 .. Definition'Last loop
               case Definition (J) is
                  when '_' | '.' | '0' .. '9' | 'a' .. 'z' | 'A' .. 'Z' =>
                     null;

                  when others =>
                     Fail ("illegal value """
                           & Definition (Index + 1 .. Definition'Last)
                           & """");
               end case;
            end loop;
         end if;

         --  Even if the value is a string, we still set Is_A_String to False,
         --  to avoid adding additional quotes in the preprocessed sources when
         --  replacing $<symbol>.

         Result.Is_A_String := False;

         --  Put the value in the result

         Start_String;
         Store_String_Chars (Definition (Index + 1 .. Definition'Last));
         Result.Value := End_String;
      end if;

      --  Now, check the syntax of the symbol (we don't allow accented or
      --  wide characters).

      if Name_Buffer (1) not in 'a' .. 'z'
        and then Name_Buffer (1) not in 'A' .. 'Z'
      then
         Fail ("symbol """
               & Name_Buffer (1 .. Name_Len)
               & """ does not start with a letter");
      end if;

      for J in 2 .. Name_Len loop
         case Name_Buffer (J) is
            when 'a' .. 'z' | 'A' .. 'Z' | '0' .. '9' =>
               null;

            when '_' =>
               if J = Name_Len then
                  Fail ("symbol """
                        & Name_Buffer (1 .. Name_Len)
                        & """ end with a '_'");

               elsif Name_Buffer (J + 1) = '_' then
                  Fail ("symbol """
                        & Name_Buffer (1 .. Name_Len)
                        & """ contains consecutive '_'");
               end if;

            when others =>
               Fail ("symbol """
                     & Name_Buffer (1 .. Name_Len)
                     & """ contains illegal character(s)");
         end case;
      end loop;

      Result.On_The_Command_Line := True;

      --  Put the symbol name in the result

      declare
         Sym : constant String := Name_Buffer (1 .. Name_Len);

      begin
         for Index in 1 .. Name_Len loop
            Name_Buffer (Index) := Fold_Lower (Name_Buffer (Index));
         end loop;

         Result.Symbol := Name_Find;
         Name_Len := Sym'Length;
         Name_Buffer (1 .. Name_Len) := Sym;
         Result.Original := Name_Find;
      end;

      Data := Result;
   end Check_Command_Line_Symbol_Definition;

   --------------
   -- Deleting --
   --------------

   function Deleting return Boolean is
   begin
      --  Always return False when not inside an #if statement

      if Opt.No_Deletion or else Pp_States.Last = Ground then
         return False;
      else
         return Pp_States.Table (Pp_States.Last).Deleting;
      end if;
   end Deleting;

   ----------------
   -- Expression --
   ----------------

   function Expression
     (Evaluate_It  : Boolean;
      Complemented : Boolean := False) return Boolean
   is
      Evaluation : Boolean := Evaluate_It;
      --  Is set to False after an "or else" when left term is True and after
      --  an "and then" when left term is False.

      Final_Result : Boolean := False;

      Current_Result : Boolean := False;
      --  Value of a term

      Current_Operator : Operator := None;
      Symbol1          : Symbol_Id;
      Symbol2          : Symbol_Id;
      Symbol_Name1     : Name_Id;
      Symbol_Name2     : Name_Id;
      Symbol_Pos1      : Source_Ptr;
      Symbol_Pos2      : Source_Ptr;
      Symbol_Value1    : String_Id;
      Symbol_Value2    : String_Id;

      Relop : Token_Type;

   begin
      --  Loop for each term

      loop
         Change_Reserved_Keyword_To_Symbol;

         Current_Result := False;

         --  Scan current term, starting with Token

         case Token is

            --  Handle parenthesized expression

            when Tok_Left_Paren =>
               Scan.all;
               Current_Result := Expression (Evaluation);

               if Token = Tok_Right_Paren then
                  Scan.all;

               else
                  Error_Msg -- CODEFIX
                    ("`)` expected", Token_Ptr);
               end if;

            --  Handle not expression

            when Tok_Not =>
               Scan.all;
               Current_Result :=
                 not Expression (Evaluation, Complemented => True);

            --  Handle sequence starting with identifier

            when Tok_Identifier =>
               Symbol_Name1 := Token_Name;
               Symbol_Pos1  := Token_Ptr;
               Scan.all;

               if Token = Tok_Apostrophe then

                  --  symbol'Defined

                  Scan.all;

                  if Token = Tok_Identifier
                    and then Token_Name = Name_Defined
                  then
                     Scan.all;

                  else
                     Error_Msg ("identifier `Defined` expected", Token_Ptr);
                  end if;

                  if Evaluation then
                     Current_Result := Index_Of (Symbol_Name1) /= No_Symbol;
                  end if;

               --  Handle relational operator

               elsif
                 Token = Tok_Equal      or else
                 Token = Tok_Less       or else
                 Token = Tok_Less_Equal or else
                 Token = Tok_Greater    or else
                 Token = Tok_Greater_Equal
               then
                  Relop := Token;
                  Scan.all;
                  Change_Reserved_Keyword_To_Symbol;

                  if Token = Tok_Integer_Literal then

                     --  symbol =  integer
                     --  symbol <  integer
                     --  symbol <= integer
                     --  symbol >  integer
                     --  symbol >= integer

                     declare
                        Value : constant Int := UI_To_Int (Int_Literal_Value);
                        Data  : Symbol_Data;

                        Symbol_Value : Int;
                        --  Value of symbol as Int

                     begin
                        if Evaluation then
                           Symbol1 := Index_Of (Symbol_Name1);

                           if Symbol1 = No_Symbol then
                              Error_Msg_Name_1 := Symbol_Name1;
                              Error_Msg ("unknown symbol %", Symbol_Pos1);
                              Symbol_Value1 := No_String;

                           else
                              Data := Mapping.Table (Symbol1);

                              if Data.Is_A_String then
                                 Error_Msg_Name_1 := Symbol_Name1;
                                 Error_Msg
                                   ("symbol % value is not integer",
                                    Symbol_Pos1);

                              else
                                 begin
                                    String_To_Name_Buffer (Data.Value);
                                    Symbol_Value :=
                                      Int'Value (Name_Buffer (1 .. Name_Len));

                                    case Relop is
                                       when Tok_Equal =>
                                          Current_Result :=
                                            Symbol_Value = Value;

                                       when Tok_Less =>
                                          Current_Result :=
                                            Symbol_Value < Value;

                                       when Tok_Less_Equal =>
                                          Current_Result :=
                                            Symbol_Value <= Value;

                                       when Tok_Greater =>
                                          Current_Result :=
                                            Symbol_Value > Value;

                                       when Tok_Greater_Equal =>
                                          Current_Result :=
                                            Symbol_Value >= Value;

                                       when others =>
                                          null;
                                    end case;

                                 exception
                                    when Constraint_Error =>
                                       Error_Msg_Name_1 := Symbol_Name1;
                                       Error_Msg
                                         ("symbol % value is not an integer",
                                          Symbol_Pos1);
                                 end;
                              end if;
                           end if;
                        end if;

                        Scan.all;
                     end;

                  --  Error if relational operator other than = if not numbers

                  elsif Relop /= Tok_Equal then
                     Error_Msg ("number expected", Token_Ptr);

                  --  Equality comparison of two strings

                  elsif Token = Tok_Identifier then

                     --  symbol = symbol

                     Symbol_Name2 := Token_Name;
                     Symbol_Pos2  := Token_Ptr;
                     Scan.all;

                     if Evaluation then
                        Symbol1 := Index_Of (Symbol_Name1);

                        if Symbol1 = No_Symbol then
                           if Undefined_Symbols_Are_False then
                              Symbol_Value1 := String_False;

                           else
                              Error_Msg_Name_1 := Symbol_Name1;
                              Error_Msg ("unknown symbol %", Symbol_Pos1);
                              Symbol_Value1 := No_String;
                           end if;

                        else
                           Symbol_Value1 :=
                             Mapping.Table (Symbol1).Value;
                        end if;

                        Symbol2 := Index_Of (Symbol_Name2);

                        if Symbol2 = No_Symbol then
                           if Undefined_Symbols_Are_False then
                              Symbol_Value2 := String_False;

                           else
                              Error_Msg_Name_1 := Symbol_Name2;
                              Error_Msg ("unknown symbol %", Symbol_Pos2);
                              Symbol_Value2 := No_String;
                           end if;

                        else
                           Symbol_Value2 := Mapping.Table (Symbol2).Value;
                        end if;

                        if Symbol_Value1 /= No_String
                             and then
                           Symbol_Value2 /= No_String
                        then
                           Current_Result :=
                             Matching_Strings (Symbol_Value1, Symbol_Value2);
                        end if;
                     end if;

                  elsif Token = Tok_String_Literal then

                     --  symbol = "value"

                     if Evaluation then
                        Symbol1 := Index_Of (Symbol_Name1);

                        if Symbol1 = No_Symbol then
                           if Undefined_Symbols_Are_False then
                              Symbol_Value1 := String_False;

                           else
                              Error_Msg_Name_1 := Symbol_Name1;
                              Error_Msg ("unknown symbol %", Symbol_Pos1);
                              Symbol_Value1 := No_String;
                           end if;

                        else
                           Symbol_Value1 := Mapping.Table (Symbol1).Value;
                        end if;

                        if Symbol_Value1 /= No_String then
                           Current_Result :=
                             Matching_Strings
                               (Symbol_Value1,
                                String_Literal_Id);
                        end if;
                     end if;

                     Scan.all;

                  else
                     Error_Msg
                       ("literal integer, symbol or literal string expected",
                        Token_Ptr);
                  end if;

               --  Handle True or False

               else
                  if Evaluation then
                     Symbol1 := Index_Of (Symbol_Name1);

                     if Symbol1 = No_Symbol then
                        if Undefined_Symbols_Are_False then
                           Symbol_Value1 := String_False;

                        else
                           Error_Msg_Name_1 := Symbol_Name1;
                           Error_Msg ("unknown symbol %", Symbol_Pos1);
                           Symbol_Value1 := No_String;
                        end if;

                     else
                        Symbol_Value1 := Mapping.Table (Symbol1).Value;
                     end if;

                     if Symbol_Value1 /= No_String then
                        String_To_Name_Buffer (Symbol_Value1);

                        for Index in 1 .. Name_Len loop
                           Name_Buffer (Index) :=
                             Fold_Lower (Name_Buffer (Index));
                        end loop;

                        if Name_Buffer (1 .. Name_Len) = "true" then
                           Current_Result := True;

                        elsif Name_Buffer (1 .. Name_Len) = "false" then
                           Current_Result := False;

                        else
                           Error_Msg_Name_1 := Symbol_Name1;
                           Error_Msg
                             ("value of symbol % is not True or False",
                              Symbol_Pos1);
                        end if;
                     end if;
                  end if;
               end if;

            --  Unrecognized sequence

            when others =>
               Error_Msg ("`(`, NOT or symbol expected", Token_Ptr);
         end case;

         --  Update the cumulative final result

         case Current_Operator is
            when None =>
               Final_Result := Current_Result;

            when Op_Or =>
               Final_Result := Final_Result or Current_Result;

            when Op_And =>
               Final_Result := Final_Result and Current_Result;
         end case;

         --  Handle AND

         if Token = Tok_And then
            if Complemented then
               Error_Msg
                ("mixing NOT and AND is not allowed, parentheses are required",
                 Token_Ptr);

            elsif Current_Operator = Op_Or then
               Error_Msg ("mixing OR and AND is not allowed", Token_Ptr);
            end if;

            Current_Operator := Op_And;
            Scan.all;

            if Token = Tok_Then then
               Scan.all;

               if Final_Result = False then
                  Evaluation := False;
               end if;
            end if;

         --  Handle OR

         elsif Token = Tok_Or then
            if Complemented then
               Error_Msg
                 ("mixing NOT and OR is not allowed, parentheses are required",
                  Token_Ptr);

            elsif Current_Operator = Op_And then
               Error_Msg ("mixing AND and OR is not allowed", Token_Ptr);
            end if;

            Current_Operator := Op_Or;
            Scan.all;

            if Token = Tok_Else then
               Scan.all;

               if Final_Result then
                  Evaluation := False;
               end if;
            end if;

         --  No AND/OR operator, so exit from the loop through terms

         else
            exit;
         end if;
      end loop;

      return Final_Result;
   end Expression;

   -----------------------
   -- Go_To_End_Of_Line --
   -----------------------

   procedure Go_To_End_Of_Line is
   begin
      --  Scan until we get an end of line or we reach the end of the buffer

      while Token /= Tok_End_Of_Line
        and then Token /= Tok_EOF
      loop
         Scan.all;
      end loop;
   end Go_To_End_Of_Line;

   --------------
   -- Index_Of --
   --------------

   function Index_Of (Symbol : Name_Id) return Symbol_Id is
   begin
      if Mapping.Table /= null then
         for J in Symbol_Id range 1 .. Symbol_Table.Last (Mapping) loop
            if Mapping.Table (J).Symbol = Symbol then
               return J;
            end if;
         end loop;
      end if;

      return No_Symbol;
   end Index_Of;

   ----------------
   -- Initialize --
   ----------------

   procedure Initialize is
   begin
      if not Already_Initialized then
         Start_String;
         Store_String_Chars ("True");
         True_Value.Value := End_String;

         Start_String;
         Empty_String := End_String;

         Start_String;
         Store_String_Chars ("False");
         String_False := End_String;

         Already_Initialized := True;
      end if;
   end Initialize;

   ------------------
   -- List_Symbols --
   ------------------

   procedure List_Symbols (Foreword : String) is
      Order : array (0 ..  Integer (Symbol_Table.Last (Mapping)))
                 of Symbol_Id;
      --  After alphabetical sorting, this array stores the indexes of the
      --  symbols in the order they are displayed.

      function Lt (Op1, Op2 : Natural) return Boolean;
      --  Comparison routine for sort call

      procedure Move (From : Natural; To : Natural);
      --  Move routine for sort call

      --------
      -- Lt --
      --------

      function Lt (Op1, Op2 : Natural) return Boolean is
         S1 : constant String :=
                Get_Name_String (Mapping.Table (Order (Op1)).Symbol);
         S2 : constant String :=
                Get_Name_String (Mapping.Table (Order (Op2)).Symbol);
      begin
         return S1 < S2;
      end Lt;

      ----------
      -- Move --
      ----------

      procedure Move (From : Natural; To : Natural) is
      begin
         Order (To) := Order (From);
      end Move;

      package Sort_Syms is new GNAT.Heap_Sort_G (Move, Lt);

      Max_L : Natural;
      --  Maximum length of any symbol

   --  Start of processing for List_Symbols_Case

   begin
      if Symbol_Table.Last (Mapping) = 0 then
         return;
      end if;

      if Foreword'Length > 0 then
         Write_Eol;
         Write_Line (Foreword);

         for J in Foreword'Range loop
            Write_Char ('=');
         end loop;
      end if;

      --  Initialize the order

      for J in Order'Range loop
         Order (J) := Symbol_Id (J);
      end loop;

      --  Sort alphabetically

      Sort_Syms.Sort (Order'Last);

      Max_L := 7;

      for J in 1 .. Symbol_Table.Last (Mapping) loop
         Get_Name_String (Mapping.Table (J).Original);
         Max_L := Integer'Max (Max_L, Name_Len);
      end loop;

      Write_Eol;
      Write_Str ("Symbol");

      for J in 1 .. Max_L - 5 loop
         Write_Char (' ');
      end loop;

      Write_Line ("Value");

      Write_Str ("------");

      for J in 1 .. Max_L - 5 loop
         Write_Char (' ');
      end loop;

      Write_Line ("------");

      for J in 1 .. Order'Last loop
         declare
            Data : constant Symbol_Data := Mapping.Table (Order (J));

         begin
            Get_Name_String (Data.Original);
            Write_Str (Name_Buffer (1 .. Name_Len));

            for K in Name_Len .. Max_L loop
               Write_Char (' ');
            end loop;

            String_To_Name_Buffer (Data.Value);

            if Data.Is_A_String then
               Write_Char ('"');

               for J in 1 .. Name_Len loop
                  Write_Char (Name_Buffer (J));

                  if Name_Buffer (J) = '"' then
                     Write_Char ('"');
                  end if;
               end loop;

               Write_Char ('"');

            else
               Write_Str (Name_Buffer (1 .. Name_Len));
            end if;
         end;

         Write_Eol;
      end loop;

      Write_Eol;
   end List_Symbols;

   ----------------------
   -- Matching_Strings --
   ----------------------

   function Matching_Strings (S1, S2 : String_Id) return Boolean is
   begin
      String_To_Name_Buffer (S1);

      for Index in 1 .. Name_Len loop
         Name_Buffer (Index) := Fold_Lower (Name_Buffer (Index));
      end loop;

      declare
         String1 : constant String := Name_Buffer (1 .. Name_Len);

      begin
         String_To_Name_Buffer (S2);

         for Index in 1 .. Name_Len loop
            Name_Buffer (Index) := Fold_Lower (Name_Buffer (Index));
         end loop;

         return String1 = Name_Buffer (1 .. Name_Len);
      end;
   end Matching_Strings;

   --------------------
   -- Parse_Def_File --
   --------------------

   --  This procedure REALLY needs some more comments ???

   procedure Parse_Def_File is
      Symbol        : Symbol_Id;
      Symbol_Name   : Name_Id;
      Original_Name : Name_Id;
      Data          : Symbol_Data;
      Value_Start   : Source_Ptr;
      Value_End     : Source_Ptr;
      Ch            : Character;

      use ASCII;

   begin
      Def_Line_Loop :
      loop
         Scan.all;

         exit Def_Line_Loop when Token = Tok_EOF;

         if Token /= Tok_End_Of_Line then
            Change_Reserved_Keyword_To_Symbol;

            if Token /= Tok_Identifier then
               Error_Msg ("identifier expected", Token_Ptr);
               goto Cleanup;
            end if;

            Symbol_Name := Token_Name;
            Name_Len := 0;

            for Ptr in Token_Ptr .. Scan_Ptr - 1 loop
               Name_Len := Name_Len + 1;
               Name_Buffer (Name_Len) := Sinput.Source (Ptr);
            end loop;

            Original_Name := Name_Find;
            Scan.all;

            if Token /= Tok_Colon_Equal then
               Error_Msg -- CODEFIX
                 ("`:=` expected", Token_Ptr);
               goto Cleanup;
            end if;

            Scan.all;

            if Token = Tok_Integer_Literal then
               declare
                  Ptr : Source_Ptr := Token_Ptr;

               begin
                  Start_String;
                  while Ptr < Scan_Ptr loop
                     Store_String_Char (Sinput.Source (Ptr));
                     Ptr := Ptr + 1;
                  end loop;

                  Data := (Symbol              => Symbol_Name,
                           Original            => Original_Name,
                           On_The_Command_Line => False,
                           Is_A_String         => False,
                           Value               => End_String);
               end;

               Scan.all;

               if Token /= Tok_End_Of_Line and then Token /= Tok_EOF then
                  Error_Msg ("extraneous text in definition", Token_Ptr);
                  goto Cleanup;
               end if;

            elsif Token = Tok_String_Literal then
               Data := (Symbol              => Symbol_Name,
                        Original            => Original_Name,
                        On_The_Command_Line => False,
                        Is_A_String         => True,
                        Value               => String_Literal_Id);

               Scan.all;

               if Token /= Tok_End_Of_Line and then Token /= Tok_EOF then
                  Error_Msg ("extraneous text in definition", Token_Ptr);
                  goto Cleanup;
               end if;

            elsif Token = Tok_End_Of_Line or else Token = Tok_EOF then
               Data := (Symbol              => Symbol_Name,
                        Original            => Original_Name,
                        On_The_Command_Line => False,
                        Is_A_String         => False,
                        Value               => Empty_String);

            else
               Value_Start := Token_Ptr;
               Value_End   := Token_Ptr - 1;
               Scan_Ptr    := Token_Ptr;

               Value_Chars_Loop :
               loop
                  Ch := Sinput.Source (Scan_Ptr);

                  case Ch is
                     when '_' | '.' | '0' .. '9' | 'a' .. 'z' | 'A' .. 'Z' =>
                        Value_End := Scan_Ptr;
                        Scan_Ptr := Scan_Ptr + 1;

                     when ' ' | HT | VT | CR | LF | FF =>
                        exit Value_Chars_Loop;

                     when others =>
                        Error_Msg ("illegal character", Scan_Ptr);
                        goto Cleanup;
                  end case;
               end loop Value_Chars_Loop;

               Scan.all;

               if Token /= Tok_End_Of_Line and then Token /= Tok_EOF then
                  Error_Msg ("extraneous text in definition", Token_Ptr);
                  goto Cleanup;
               end if;

               Start_String;

               while Value_Start <= Value_End loop
                  Store_String_Char (Sinput.Source (Value_Start));
                  Value_Start := Value_Start + 1;
               end loop;

               Data := (Symbol              => Symbol_Name,
                        Original            => Original_Name,
                        On_The_Command_Line => False,
                        Is_A_String         => False,
                        Value               => End_String);
            end if;

            --  Now that we have the value, get the symbol index

            Symbol := Index_Of (Symbol_Name);

            if Symbol /= No_Symbol then

               --  If we already have an entry for this symbol, replace it
               --  with the new value, except if the symbol was declared on
               --  the command line.

               if Mapping.Table (Symbol).On_The_Command_Line then
                  goto Continue;
               end if;

            else
               --  As it is the first time we see this symbol, create a new
               --  entry in the table.

               if Mapping.Table = null then
                  Symbol_Table.Init (Mapping);
               end if;

               Symbol_Table.Increment_Last (Mapping);
               Symbol := Symbol_Table.Last (Mapping);
            end if;

            Mapping.Table (Symbol) := Data;
            goto Continue;

            <<Cleanup>>
               Set_Ignore_Errors (To => True);

               while Token /= Tok_End_Of_Line and then Token /= Tok_EOF loop
                  Scan.all;
               end loop;

               Set_Ignore_Errors (To => False);

            <<Continue>>
               null;
         end if;
      end loop Def_Line_Loop;
   end Parse_Def_File;

   ----------------
   -- Preprocess --
   ----------------

   procedure Preprocess (Source_Modified : out Boolean) is
      Start_Of_Processing : Source_Ptr;
      Cond                : Boolean;
      Preprocessor_Line   : Boolean := False;
      No_Error_Found      : Boolean := True;
      Modified            : Boolean := False;

      procedure Output (From, To : Source_Ptr);
      --  Output the characters with indexes From .. To in the buffer to the
      --  output file.

      procedure Output_Line (From, To : Source_Ptr);
      --  Output a line or the end of a line from the buffer to the output
      --  file, followed by an end of line terminator. Depending on the value
      --  of Deleting and the switches, the line may be commented out, blank or
      --  not output at all.

      ------------
      -- Output --
      ------------

      procedure Output (From, To : Source_Ptr) is
      begin
         for J in From .. To loop
            Put_Char (Sinput.Source (J));
         end loop;
      end Output;

      -----------------
      -- Output_Line --
      -----------------

      procedure Output_Line (From, To : Source_Ptr) is
      begin
         if Deleting or else Preprocessor_Line then
            if Blank_Deleted_Lines then
               New_EOL.all;

            elsif Comment_Deleted_Lines then
               Put_Char ('-');
               Put_Char ('-');
               Put_Char ('!');

               if From < To then
                  Put_Char (' ');
                  Output (From, To);
               end if;

               New_EOL.all;
            end if;

         else
            Output (From, To);
            New_EOL.all;
         end if;
      end Output_Line;

   --  Start of processing for Preprocess

   begin
      Start_Of_Processing := Scan_Ptr;

      --  First a call to Scan, because Initialize_Scanner is not doing it

      Scan.all;

      Input_Line_Loop : loop
         exit Input_Line_Loop when Token = Tok_EOF;

         Preprocessor_Line := False;

         if Token /= Tok_End_Of_Line then

            --  Preprocessor line

            if Token = Tok_Special and then Special_Character = '#' then
               Modified := True;
               Preprocessor_Line := True;
               Scan.all;

               case Token is

                  --  #if

                  when Tok_If =>
                     declare
                        If_Ptr : constant Source_Ptr := Token_Ptr;

                     begin
                        Scan.all;
                        Cond := Expression (not Deleting);

                        --  Check for an eventual "then"

                        if Token = Tok_Then then
                           Scan.all;
                        end if;

                        --  It is an error to have trailing characters after
                        --  the condition or "then".

                        if Token /= Tok_End_Of_Line
                          and then Token /= Tok_EOF
                        then
                           Error_Msg
                             ("extraneous text on preprocessor line",
                              Token_Ptr);
                           No_Error_Found := False;
                           Go_To_End_Of_Line;
                        end if;

                        declare
                           --  Set the initial state of this new "#if". This
                           --  must be done before incrementing the Last of
                           --  the table, otherwise function Deleting does
                           --  not report the correct value.

                           New_State : constant Pp_State :=
                                         (If_Ptr     => If_Ptr,
                                          Else_Ptr   => 0,
                                          Deleting   => Deleting
                                                          or else not Cond,
                                          Match_Seen => Deleting or else Cond);

                        begin
                           Pp_States.Increment_Last;
                           Pp_States.Table (Pp_States.Last) := New_State;
                        end;
                     end;

                  --  #elsif

                  when Tok_Elsif =>
                     Cond := False;

                     if Pp_States.Last = 0
                       or else Pp_States.Table (Pp_States.Last).Else_Ptr /= 0
                     then
                        Error_Msg ("no IF for this ELSIF", Token_Ptr);
                        No_Error_Found := False;

                     else
                        Cond :=
                          not Pp_States.Table (Pp_States.Last).Match_Seen;
                     end if;

                     Scan.all;
                     Cond := Expression (Cond);

                     --  Check for an eventual "then"

                     if Token = Tok_Then then
                        Scan.all;
                     end if;

                     --  It is an error to have trailing characters after the
                     --  condition or "then".

                     if Token /= Tok_End_Of_Line
                       and then Token /= Tok_EOF
                     then
                        Error_Msg
                          ("extraneous text on preprocessor line",
                           Token_Ptr);
                        No_Error_Found := False;

                        Go_To_End_Of_Line;
                     end if;

                     --  Depending on the value of the condition, set the new
                     --  values of Deleting and Match_Seen.

                     if Pp_States.Last > 0 then
                        if Pp_States.Table (Pp_States.Last).Match_Seen then
                           Pp_States.Table (Pp_States.Last).Deleting := True;
                        else
                           if Cond then
                              Pp_States.Table (Pp_States.Last).Match_Seen :=
                                True;
                              Pp_States.Table (Pp_States.Last).Deleting :=
                                False;
                           end if;
                        end if;
                     end if;

                  --  #else

                  when Tok_Else =>
                     if Pp_States.Last = 0 then
                        Error_Msg ("no IF for this ELSE", Token_Ptr);
                        No_Error_Found := False;

                     elsif
                       Pp_States.Table (Pp_States.Last).Else_Ptr /= 0
                     then
                        Error_Msg -- CODEFIX
                          ("duplicate ELSE line", Token_Ptr);
                        No_Error_Found := False;
                     end if;

                     --  Set the possibly new values of Deleting and Match_Seen

                     if Pp_States.Last > 0 then
                        if Pp_States.Table (Pp_States.Last).Match_Seen then
                           Pp_States.Table (Pp_States.Last).Deleting :=
                             True;

                        else
                           Pp_States.Table (Pp_States.Last).Match_Seen :=
                             True;
                           Pp_States.Table (Pp_States.Last).Deleting :=
                             False;
                        end if;

                        --  Set the Else_Ptr to check for illegal #elsif later

                        Pp_States.Table (Pp_States.Last).Else_Ptr :=
                          Token_Ptr;
                     end if;

                     Scan.all;

                     --  Error of character present after "#else"

                     if Token /= Tok_End_Of_Line
                       and then Token /= Tok_EOF
                     then
                        Error_Msg
                          ("extraneous text on preprocessor line",
                           Token_Ptr);
                        No_Error_Found := False;
                        Go_To_End_Of_Line;
                     end if;

                  --  #end if;

                  when Tok_End =>
                     if Pp_States.Last = 0 then
                        Error_Msg ("no IF for this END", Token_Ptr);
                        No_Error_Found := False;
                     end if;

                     Scan.all;

                     if Token /= Tok_If then
                        Error_Msg -- CODEFIX
                          ("IF expected", Token_Ptr);
                        No_Error_Found := False;

                     else
                        Scan.all;

                        if Token /= Tok_Semicolon then
                           Error_Msg -- CODEFIX
                             ("`;` Expected", Token_Ptr);
                           No_Error_Found := False;

                        else
                           Scan.all;

                           --  Error of character present after "#end if;"

                           if Token /= Tok_End_Of_Line
                             and then Token /= Tok_EOF
                           then
                              Error_Msg
                                ("extraneous text on preprocessor line",
                                 Token_Ptr);
                              No_Error_Found := False;
                           end if;
                        end if;
                     end if;

                     --  In case of one of the errors above, skip the tokens
                     --  until the end of line is reached.

                     Go_To_End_Of_Line;

                     --  Decrement the depth of the #if stack

                     if Pp_States.Last > 0 then
                        Pp_States.Decrement_Last;
                     end if;

                  --  Illegal preprocessor line

                  when others =>
                     No_Error_Found := False;

                     if Pp_States.Last = 0 then
                        Error_Msg -- CODEFIX
                          ("IF expected", Token_Ptr);

                     elsif
                       Pp_States.Table (Pp_States.Last).Else_Ptr = 0
                     then
                        Error_Msg
                          ("IF, ELSIF, ELSE, or `END IF` expected",
                           Token_Ptr);

                     else
                        Error_Msg ("IF or `END IF` expected", Token_Ptr);
                     end if;

                     --  Skip to the end of this illegal line

                     Go_To_End_Of_Line;
               end case;

            --  Not a preprocessor line

            else
               --  Do not report errors for those lines, even if there are
               --  Ada parsing errors.

               Set_Ignore_Errors (To => True);

               if Deleting then
                  Go_To_End_Of_Line;

               else
                  while Token /= Tok_End_Of_Line
                    and then Token /= Tok_EOF
                  loop
                     if Token = Tok_Special
                       and then Special_Character = '$'
                     then
                        Modified := True;

                        declare
                           Dollar_Ptr : constant Source_Ptr := Token_Ptr;
                           Symbol     : Symbol_Id;

                        begin
                           Scan.all;
                           Change_Reserved_Keyword_To_Symbol;

                           if Token = Tok_Identifier
                             and then Token_Ptr = Dollar_Ptr + 1
                           then
                              --  $symbol

                              Symbol := Index_Of (Token_Name);

                              --  If symbol exists, replace by its value

                              if Symbol /= No_Symbol then
                                 Output (Start_Of_Processing, Dollar_Ptr - 1);
                                 Start_Of_Processing := Scan_Ptr;
                                 String_To_Name_Buffer
                                   (Mapping.Table (Symbol).Value);

                                 if Mapping.Table (Symbol).Is_A_String then

                                    --  Value is an Ada string

                                    Put_Char ('"');

                                    for J in 1 .. Name_Len loop
                                       Put_Char (Name_Buffer (J));

                                       if Name_Buffer (J) = '"' then
                                          Put_Char ('"');
                                       end if;
                                    end loop;

                                    Put_Char ('"');

                                 else
                                    --  Value is a sequence of characters, not
                                    --  an Ada string.

                                    for J in 1 .. Name_Len loop
                                       Put_Char (Name_Buffer (J));
                                    end loop;
                                 end if;
                              end if;
                           end if;
                        end;
                     end if;

                     Scan.all;
                  end loop;
               end if;

               Set_Ignore_Errors (To => False);
            end if;
         end if;

         pragma Assert (Token = Tok_End_Of_Line or else Token = Tok_EOF);

         --  At this point, the token is either end of line or EOF. The line to
         --  possibly output stops just before the token.

         Output_Line (Start_Of_Processing, Token_Ptr - 1);

         --  If we are at the end of a line, the scan pointer is at the first
         --  non-blank character (may not be the first character of the line),
         --  so we have to deduct Start_Of_Processing from the token pointer.

         if Token = Tok_End_Of_Line then
            if (Sinput.Source (Token_Ptr) = ASCII.CR
                  and then Sinput.Source (Token_Ptr + 1) = ASCII.LF)
              or else
               (Sinput.Source (Token_Ptr) = ASCII.CR
                  and then Sinput.Source (Token_Ptr + 1) = ASCII.LF)
            then
               Start_Of_Processing := Token_Ptr + 2;
            else
               Start_Of_Processing := Token_Ptr + 1;
            end if;
         end if;

         --  Now, scan the first token of the next line. If the token is EOF,
         --  the scan pointer will not move, and the token will still be EOF.

         Set_Ignore_Errors (To => True);
         Scan.all;
         Set_Ignore_Errors (To => False);
      end loop Input_Line_Loop;

      --  Report an error for any missing some "#end if;"

      for Level in reverse 1 .. Pp_States.Last loop
         Error_Msg ("no `END IF` for this IF", Pp_States.Table (Level).If_Ptr);
         No_Error_Found := False;
      end loop;

      Source_Modified := No_Error_Found and Modified;
   end Preprocess;

   -----------------
   -- Setup_Hooks --
   -----------------

   procedure Setup_Hooks
     (Error_Msg         : Error_Msg_Proc;
      Scan              : Scan_Proc;
      Set_Ignore_Errors : Set_Ignore_Errors_Proc;
      Put_Char          : Put_Char_Proc;
      New_EOL           : New_EOL_Proc)
   is
   begin
      pragma Assert (Already_Initialized);

      Prep.Error_Msg         := Error_Msg;
      Prep.Scan              := Scan;
      Prep.Set_Ignore_Errors := Set_Ignore_Errors;
      Prep.Put_Char          := Put_Char;
      Prep.New_EOL           := New_EOL;
   end Setup_Hooks;

end Prep;
