------------------------------------------------------------------------------
--                                                                          --
--                         GNAT COMPILER COMPONENTS                         --
--                                                                          --
--                                G P R E P                                 --
--                                                                          --
--                                 B o d y                                  --
--                                                                          --
--          Copyright (C) 2002-2004, 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,  59 Temple Place - Suite 330,  Boston, --
-- MA 02111-1307, USA.                                                      --
--                                                                          --
-- GNAT was originally developed  by the GNAT team at  New York University. --
-- Extensive contributions were provided by Ada Core Technologies Inc.      --
--                                                                          --
------------------------------------------------------------------------------

with Csets;
with Err_Vars; use Err_Vars;
with Errutil;
with Gnatvsn;
with Namet;    use Namet;
with Opt;
with Osint;    use Osint;
with Output;   use Output;
with Prep;     use Prep;
with Scng;
with Sinput.C;
with Snames;
with Stringt;  use Stringt;
with Types;    use Types;

with Ada.Text_IO;       use Ada.Text_IO;
with GNAT.Command_Line;
with GNAT.OS_Lib;       use GNAT.OS_Lib;

package body GPrep is

   Copyright_Displayed : Boolean := False;
   --  Used to prevent multiple displays of the copyright notice

   ------------------------
   -- Argument Line Data --
   ------------------------

   Infile_Name  : String_Access;
   Outfile_Name : String_Access;
   Deffile_Name : String_Access;

   Source_Ref_Pragma : Boolean := False;
   --  Record command line options (set if -r switch set)

   Text_Outfile : aliased Ada.Text_IO.File_Type;
   Outfile      : constant File_Access := Text_Outfile'Access;

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

   procedure Display_Copyright;
   --  Display the copyright notice

   procedure Post_Scan;
   --  Null procedure, needed by instantiation of Scng below

   package Scanner is new Scng
     (Post_Scan,
      Errutil.Error_Msg,
      Errutil.Error_Msg_S,
      Errutil.Error_Msg_SC,
      Errutil.Error_Msg_SP,
      Errutil.Style);
   --  The scanner for the preprocessor

   procedure Process_Command_Line_Symbol_Definition (S : String);
   --  Process a -D switch on ther command line

   procedure Put_Char_To_Outfile (C : Character);
   --  Output one character to the output file.
   --  Used to initialize the preprocessor.

   procedure New_EOL_To_Outfile;
   --  Output a new line to the output file.
   --  Used to initialize the preprocessor.

   procedure Scan_Command_Line;
   --  Scan the switches and the file names

   procedure Usage;
   --  Display the usage

   -----------------------
   -- Display_Copyright --
   -----------------------

   procedure Display_Copyright is
   begin
      if not Copyright_Displayed then
         Write_Line ("GNAT Preprocessor " &
                     Gnatvsn.Gnat_Version_String &
                     " Copyright 1996-2004 Free Software Foundation, Inc.");
         Copyright_Displayed := True;
      end if;
   end Display_Copyright;

   --------------
   -- Gnatprep --
   --------------

   procedure Gnatprep is
      Infile : Source_File_Index;

   begin
      --  Do some initializations (order is important here!)

      Csets.Initialize;
      Namet.Initialize;
      Snames.Initialize;
      Stringt.Initialize;

      --  Initialize the preprocessor

      Prep.Initialize
        (Error_Msg         => Errutil.Error_Msg'Access,
         Scan              => Scanner.Scan'Access,
         Set_Ignore_Errors => Errutil.Set_Ignore_Errors'Access,
         Put_Char          => Put_Char_To_Outfile'Access,
         New_EOL           => New_EOL_To_Outfile'Access);

      --  Set the scanner characteristics for the preprocessor

      Scanner.Set_Special_Character ('#');
      Scanner.Set_Special_Character ('$');
      Scanner.Set_End_Of_Line_As_Token (True);

      --  Initialize the mapping table of symbols to values

      Prep.Symbol_Table.Init (Prep.Mapping);

      --  Parse the switches and arguments

      Scan_Command_Line;

      if Opt.Verbose_Mode then
         Display_Copyright;
      end if;

      --  Test we had all the arguments needed

      if Infile_Name = null then
         --  No input file specified, just output the usage and exit

         Usage;
         return;
      elsif Outfile_Name = null then
         --  No output file specified, just output the usage and exit

         Usage;
         return;
      end if;

      --  If a pragma Source_File_Name, we need to keep line numbers.
      --  So, if the deleted lines are not put as comment, we must output them
      --  as blank lines.

      if Source_Ref_Pragma and (not Opt.Comment_Deleted_Lines) then
         Opt.Blank_Deleted_Lines := True;
      end if;

      --  If we have a definition file, parse it

      if Deffile_Name /= null then
         declare
            Deffile : Source_File_Index;

         begin
            Errutil.Initialize;
            Deffile := Sinput.C.Load_File (Deffile_Name.all);

            --  Set Main_Source_File to the definition file for the benefit of
            --  Errutil.Finalize.

            Sinput.Main_Source_File := Deffile;

            if Deffile = No_Source_File then
               Fail ("unable to find definition file """,
                     Deffile_Name.all,
                     """");
            end if;

            Scanner.Initialize_Scanner (No_Unit, Deffile);

            Prep.Parse_Def_File;
         end;
      end if;

      --  If there are errors in the definition file, output these errors
      --  and exit.

      if Total_Errors_Detected > 0 then
         Errutil.Finalize (Source_Type => "definition");
         Fail ("errors in definition file """, Deffile_Name.all, """");
      end if;

      --  If -s switch was specified, print a sorted list of symbol names and
      --  values, if any.

      if Opt.List_Preprocessing_Symbols then
         Prep.List_Symbols (Foreword => "");
      end if;

      --  Load the input file

      Infile := Sinput.C.Load_File (Infile_Name.all);

      if Infile = No_Source_File then
         Fail ("unable to find input file """, Infile_Name.all, """");
      end if;

      --  Set Main_Source_File to the input file for the benefit of
      --  Errutil.Finalize.

      Sinput.Main_Source_File := Infile;

      Scanner.Initialize_Scanner (No_Unit, Infile);

      --  If an output file were specified, create it; fails if this did not
      --  work.

      if Outfile_Name /= null then
         begin
            Create (Text_Outfile, Out_File, Outfile_Name.all);

         exception
            when others =>
               Fail
                 ("unable to create output file """, Outfile_Name.all, """");
         end;
      end if;

      --  Output the SFN pragma if asked to

      if Source_Ref_Pragma then
         Put_Line (Outfile.all, "pragma Source_Reference (1, """ &
                   Get_Name_String (Sinput.File_Name (Infile)) &
                   """);");
      end if;

      --  Preprocess the input file

      Prep.Preprocess;

      --  In verbose mode, if there is no error, report it

      if Opt.Verbose_Mode and then Err_Vars.Total_Errors_Detected = 0 then
         Errutil.Finalize (Source_Type => "input");
      end if;

      --  If we had some errors, delete the output file, and report the errors,

      if Err_Vars.Total_Errors_Detected > 0 then
         if Outfile /= Standard_Output then
            Delete (Text_Outfile);
         end if;

         Errutil.Finalize (Source_Type => "input");

      --  otherwise, close the output file, and we are done.

      elsif Outfile /= Standard_Output then
         Close (Text_Outfile);
      end if;
   end Gnatprep;

   ------------------------
   -- New_EOL_To_Outfile --
   ------------------------

   procedure New_EOL_To_Outfile is
   begin
      New_Line (Outfile.all);
   end New_EOL_To_Outfile;

   ---------------
   -- Post_Scan --
   ---------------

   procedure Post_Scan is
   begin
      null;
   end Post_Scan;

   --------------------------------------------
   -- Process_Command_Line_Symbol_Definition --
   --------------------------------------------

   procedure Process_Command_Line_Symbol_Definition (S : String) is
      Data   : Symbol_Data;
      Symbol : Symbol_Id;

   begin
      --  Check the symbol definition and get the symbol and its value.
      --  Fail if symbol definition is illegal.

      Check_Command_Line_Symbol_Definition (S, Data);

      Symbol := Index_Of (Data.Symbol);

      --  If symbol does not alrady exist, create a new entry in the mapping
      --  table.

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

      Mapping.Table (Symbol) := Data;
   end Process_Command_Line_Symbol_Definition;

   -------------------------
   -- Put_Char_To_Outfile --
   -------------------------

   procedure Put_Char_To_Outfile (C : Character) is
   begin
      Put (Outfile.all, C);
   end Put_Char_To_Outfile;

   -----------------------
   -- Scan_Command_Line --
   -----------------------

   procedure Scan_Command_Line is
      Switch : Character;

   begin
      --  Parse the switches

      loop
         begin
            Switch := GNAT.Command_Line.Getopt ("D: b c r s u v");
            case Switch is

               when ASCII.NUL =>
                  exit;

               when 'D' =>
                  Process_Command_Line_Symbol_Definition
                    (S => GNAT.Command_Line.Parameter);

               when 'b' =>
                  Opt.Blank_Deleted_Lines := True;

               when 'c' =>
                  Opt.Comment_Deleted_Lines := True;

               when 'r' =>
                  Source_Ref_Pragma := True;

               when 's' =>
                  Opt.List_Preprocessing_Symbols := True;

               when 'u' =>
                  Opt.Undefined_Symbols_Are_False := True;

               when 'v' =>
                  Opt.Verbose_Mode := True;

               when others =>
                  Fail ("Invalid Switch: -" & Switch);
            end case;

         exception
            when GNAT.Command_Line.Invalid_Switch =>
               Write_Str ("Invalid Switch: -");
               Write_Line (GNAT.Command_Line.Full_Switch);
               Usage;
               OS_Exit (1);
         end;
      end loop;

      --  Get the file names

      loop
         declare
            S : constant String := GNAT.Command_Line.Get_Argument;

         begin
            exit when S'Length = 0;

            if Infile_Name = null then
               Infile_Name := new String'(S);
            elsif Outfile_Name = null then
               Outfile_Name := new String'(S);
            elsif Deffile_Name = null then
               Deffile_Name := new String'(S);
            else
               Fail ("too many arguments specifed");
            end if;
         end;
      end loop;
   end Scan_Command_Line;

   -----------
   -- Usage --
   -----------

   procedure Usage is
   begin
      Display_Copyright;
      Write_Line ("Usage: gnatprep [-bcrsuv] [-Dsymbol=value] " &
                    "infile outfile [deffile]");
      Write_Eol;
      Write_Line ("  infile     Name of the input file");
      Write_Line ("  outfile    Name of the output file");
      Write_Line ("  deffile    Name of the definition file");
      Write_Eol;
      Write_Line ("gnatprep switches:");
      Write_Line ("   -b  Replace preprocessor lines by blank lines");
      Write_Line ("   -c  Keep preprocessor lines as comments");
      Write_Line ("   -D  Associate symbol with value");
      Write_Line ("   -r  Generate Source_Reference pragma");
      Write_Line ("   -s  Print a sorted list of symbol names and values");
      Write_Line ("   -u  Treat undefined symbols as FALSE");
      Write_Line ("   -v  Verbose mode");
      Write_Eol;
   end Usage;

end GPrep;