-------------------------------------------------------------------------------
-- (C) Altran Praxis Limited
-------------------------------------------------------------------------------
--
-- The SPARK toolset is free software; you can redistribute it and/or modify it
-- under terms of the GNU General Public License as published by the Free
-- Software Foundation; either version 3, or (at your option) any later
-- version. The SPARK toolset is distributed in the hope that it will be
-- useful, but WITHOUT 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 the SPARK toolset; see file
-- COPYING3. If not, go to http://www.gnu.org/licenses for a complete copy of
-- the license.
--
--=============================================================================

separate (SparkLex)
package body LineManager is

   procedure Clear_Line (Curr_Line : out SparkLex.Line_Context) is
   begin
      Curr_Line :=
        SparkLex.Line_Context'
        (Context        => SparkLex.In_Ada,
         Anno_Context   => SparkLex.Start_Annotation,
         Line_No        => LexTokenManager.Line_Numbers'First,
         Last_Token_Pos => E_Strings.Positions'First,
         Curr_Pos       => E_Strings.Positions'First,
         Lookahead_Pos  => E_Strings.Positions'First,
         Conts          => E_Strings.Get_Empty_String);
   end Clear_Line;

   procedure Copy_In_Line (Line      : in     SparkLex.Line_Context;
                           Curr_Line :    out SparkLex.Line_Context) is
   begin
      Curr_Line := Line;
   end Copy_In_Line;

   procedure Record_Curr_Pos (Curr_Line : in out SparkLex.Line_Context) is
   begin
      Curr_Line.Last_Token_Pos := Curr_Line.Curr_Pos;
   end Record_Curr_Pos;

   procedure Reset_Curr_Pos (Curr_Line : in out SparkLex.Line_Context) is
   begin
      Curr_Line.Curr_Pos      := Curr_Line.Last_Token_Pos;
      Curr_Line.Lookahead_Pos := Curr_Line.Last_Token_Pos;
   end Reset_Curr_Pos;

   procedure Accept_Char (Curr_Line : in out SparkLex.Line_Context) is
   begin
      if Curr_Line.Curr_Pos <= E_Strings.Get_Length (E_Str => Curr_Line.Conts) then
         Curr_Line.Curr_Pos := Curr_Line.Curr_Pos + 1;
      end if;
      Curr_Line.Lookahead_Pos := Curr_Line.Curr_Pos;
   end Accept_Char;

   procedure Lookahead_Char (Curr_Line : in out SparkLex.Line_Context;
                             Ch        :    out Character) is
   begin
      if Curr_Line.Lookahead_Pos > E_Strings.Get_Length (E_Str => Curr_Line.Conts) then
         if Curr_Line.Lookahead_Pos > 1
           and then E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                           Pos   => Curr_Line.Lookahead_Pos - 1) = SparkLex.End_Of_Text
         then
            Ch := SparkLex.End_Of_Text;
         else
            Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                         Pos   => Curr_Line.Lookahead_Pos);
         end if;
      else
         case E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                     Pos   => Curr_Line.Lookahead_Pos) is
            when SparkLex.End_Of_Text =>
               Ch                      := SparkLex.End_Of_Text;
               Curr_Line.Lookahead_Pos := Curr_Line.Lookahead_Pos + 1;
            when others =>
               Curr_Line.Lookahead_Pos := Curr_Line.Lookahead_Pos + 1;
               Ch                      := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                                 Pos   => Curr_Line.Lookahead_Pos);
         end case;
      end if;
   end Lookahead_Char;

   procedure Accept_Lookahead (Curr_Line : in out SparkLex.Line_Context) is
   begin
      if Curr_Line.Lookahead_Pos <= E_Strings.Get_Length (E_Str => Curr_Line.Conts) then
         Curr_Line.Lookahead_Pos := Curr_Line.Lookahead_Pos + 1;
      end if;
      Curr_Line.Curr_Pos := Curr_Line.Lookahead_Pos;
   end Accept_Lookahead;

   procedure Reject_Lookahead (Curr_Line : in out SparkLex.Line_Context) is
   begin
      Curr_Line.Lookahead_Pos := Curr_Line.Curr_Pos;
   end Reject_Lookahead;

   function Separator (Ch : Character) return Boolean is
   begin
      return Ch = ' '
        or else Ch = Ada.Characters.Latin_1.HT
        or else Ch = Ada.Characters.Latin_1.VT
        or else Ch = Ada.Characters.Latin_1.CR
        or else Ch = Ada.Characters.Latin_1.LF
        or else Ch = Ada.Characters.Latin_1.FF;
   end Separator;

   procedure Next_Sig_Char (Prog_Text : in     SPARK_IO.File_Type;
                            Curr_Line : in out SparkLex.Line_Context) is
      Loc_Pos : E_Strings.Positions;
      Ch      : Character;

      procedure Get_Next_Line (Prog_Text : in     SPARK_IO.File_Type;
                               Curr_Line : in out SparkLex.Line_Context)
      --# global in     CommandLineData.Content;
      --#        in     Dictionary.Dict;
      --#        in out ErrorHandler.Error_Context;
      --#        in out LexTokenManager.State;
      --#        in out SPARK_IO.File_Sys;
      --# derives Curr_Line,
      --#         ErrorHandler.Error_Context,
      --#         LexTokenManager.State,
      --#         SPARK_IO.File_Sys          from CommandLineData.Content,
      --#                                         Curr_Line,
      --#                                         Dictionary.Dict,
      --#                                         ErrorHandler.Error_Context,
      --#                                         LexTokenManager.State,
      --#                                         Prog_Text,
      --#                                         SPARK_IO.File_Sys;
      --# post E_Strings.Get_Length (Curr_Line.Conts) < Natural'Last and
      --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1;
      is
         Pos                : E_Strings.Positions;
         End_Pos            : E_Strings.Lengths;
         Line               : E_Strings.T;
         Line_No_Before_Get : Positive;

         procedure Inc_Line_Number (Curr_Line : in out SparkLex.Line_Context)
         --# derives Curr_Line from *;
         --# post E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts);
         is
         begin
            if Curr_Line.Line_No = LexTokenManager.Line_Numbers'Last then
               SystemErrors.Fatal_Error (Sys_Err => SystemErrors.Too_Many_File_Lines,
                                         Msg     => "in SparkLex.LineManager");
            end if;
            Curr_Line.Line_No := Curr_Line.Line_No + 1;
         end Inc_Line_Number;

      begin -- Get_Next_Line
         Curr_Line.Conts := E_Strings.Get_Empty_String;
         loop
            --# assert E_Strings.Get_Length (Curr_Line.Conts) < Natural'Last;
            if SPARK_IO.End_Of_Line (Prog_Text) then
               -- Skip empty line
               if SPARK_IO.End_Of_File (Prog_Text) then
                  Curr_Line.Conts := E_Strings.Get_Empty_String;
                  E_Strings.Append_Char (E_Str => Curr_Line.Conts,
                                         Ch    => SparkLex.End_Of_Text);
               else
                  SPARK_IO.Skip_Line (Prog_Text, 1);
                  Inc_Line_Number (Curr_Line => Curr_Line);
               end if;
            else
               -- Attempt to read the line
               Line_No_Before_Get := SPARK_IO.Line (Prog_Text);
               E_Strings.Get_Line (File  => Prog_Text,
                                   E_Str => Line);
               Inc_Line_Number (Curr_Line => Curr_Line);
               if E_Strings.Get_Length (E_Str => Line) = 0 then -- Examiner bug - OK but not acccepted

                  -- Unable to read line, eight-bit character?
                  ErrorHandler.Lex_Error
                    (Error_Message    => "Line contains illegal character(s)",
                     Recovery_Message => "Ignored",
                     Error_Item       => LexTokenManager.Lex_Value'(Position  => LexTokenManager.Token_Position'(Start_Line_No => Curr_Line.Line_No,
                                                                                                                 Start_Pos     => 2),
                                                                    Token_Str => LexTokenManager.Null_String));
                  SPARK_IO.Skip_Line (Prog_Text, 1);
                  if Line_No_Before_Get = SPARK_IO.Line (Prog_Text) then
                     Curr_Line.Conts := E_Strings.Get_Empty_String;
                     E_Strings.Append_Char (E_Str => Curr_Line.Conts,
                                            Ch    => SparkLex.End_Of_Text);
                  end if;
               elsif E_Strings.Get_Length (E_Str => Line) < Natural'Last then
                  -- got a line!
                  Curr_Line.Conts := Line;
               else
                  SystemErrors.Fatal_Error
                    (Sys_Err => SystemErrors.Other_Internal_Error,
                     Msg     => "Line too long in SparkLex.LineManager");
               end if;
            end if;

            -- Skip over leading separators.
            Pos     := 1;
            End_Pos := E_Strings.Get_Length (E_Str => Curr_Line.Conts);
            loop
               exit when Pos >= End_Pos;
               --# assert Pos < End_Pos and End_Pos = E_Strings.Get_Length (Curr_Line.Conts) and
               --#   E_Strings.Get_Length (Curr_Line.Conts) < Natural'Last;
               exit when not Separator (Ch => E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                                     Pos   => Pos));
               Pos := Pos + 1;
            end loop;

            exit when not Separator (Ch => E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                                  Pos   => Pos));
            exit when End_Pos > 0
              and then E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                              Pos   => End_Pos) = SparkLex.End_Of_Text;
         end loop;

         -- Context sensitive annotation continuation check
         if Curr_Line.Context = SparkLex.In_Annotation
           and then (End_Pos - Pos < 2
                       or else E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                      Pos   => Pos) /= '-'
                       or else E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                      Pos   => Pos + 1) /= '-'
                       or else E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                      Pos   => Pos + 2) /=
                       CommandLineData.Content.Anno_Char) then
            Pos               := 1;
            Curr_Line.Context := SparkLex.In_Ada;
         end if;

         Curr_Line.Curr_Pos      := Pos;
         Curr_Line.Lookahead_Pos := Pos;
      end Get_Next_Line;

   begin -- Next_Sig_Char
      Loc_Pos := Curr_Line.Curr_Pos;
      loop
         --# assert Loc_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
         --#   E_Strings.Get_Length (Curr_Line.Conts) < Natural'Last;
         if Loc_Pos > E_Strings.Get_Length (E_Str => Curr_Line.Conts) then
            Get_Next_Line (Prog_Text => Prog_Text,
                           Curr_Line => Curr_Line);
            Loc_Pos := Curr_Line.Curr_Pos;
         end if;
         Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                      Pos   => Loc_Pos);
         if Loc_Pos <= E_Strings.Get_Length (E_Str => Curr_Line.Conts)
           and then Ch /= ' '
           and then Ch /= Ada.Characters.Latin_1.HT
           and then Ch /= Ada.Characters.Latin_1.VT
           and then Ch /= Ada.Characters.Latin_1.LF
           and then Ch /= Ada.Characters.Latin_1.FF then
            exit;
         end if;
         if Loc_Pos <= E_Strings.Get_Length (E_Str => Curr_Line.Conts) then
            Loc_Pos := Loc_Pos + 1;  -- Skip separator
         end if;
      end loop;
      Curr_Line.Last_Token_Pos := Loc_Pos;
      Curr_Line.Curr_Pos       := Loc_Pos;
      Curr_Line.Lookahead_Pos  := Loc_Pos;
   end Next_Sig_Char;

   procedure Set_Context (Curr_Line   : in out SparkLex.Line_Context;
                          New_Context : in     SparkLex.Program_Context) is
   begin
      Curr_Line.Context := New_Context;
   end Set_Context;

   procedure Set_Anno_Context (Curr_Line   : in out SparkLex.Line_Context;
                               New_Context : in     SparkLex.Annotation_Context) is
   begin
      Curr_Line.Anno_Context := New_Context;
   end Set_Anno_Context;

end LineManager;
