-------------------------------------------------------------------------------
-- (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 (SPARKProgram.Reformatter)
package body SimpleLex is

   procedure Initialise (Input_File : in     SPARK_IO.File_Type;
                         Anno       : in     Annotations.Anno_Type;
                         Lex_State  :    out State) is
   begin
      Lex_State :=
        State'
        (File          => Input_File,
         Anno          => Anno,
         Line          => E_Strings.Empty_String,
         Index         => E_Strings.Positions'First,
         In_Annotation => False);
   end Initialise;

   procedure Next (This      : in out State;
                   Token_Rec :    out Token_Record) is
      Index             : E_Strings.Positions;
      Input_File        : SPARK_IO.File_Type;
      Anno              : Annotations.Anno_Type;
      Now_In_Annotation : Boolean;

      function Is_Alphanumeric (Ch : Character) return Boolean is
      begin
         return Ada.Characters.Handling.Is_Letter (Ch) or else Ada.Characters.Handling.Is_Digit (Ch);
      end Is_Alphanumeric;

      procedure Get_Identifier (Input_Line : in     E_Strings.T;
                                Index      : in out E_Strings.Positions;
                                Token_Rec  :    out Token_Record)
      --# derives Index,
      --#         Token_Rec from Index,
      --#                        Input_Line;
      is
         Start_Pos : E_Strings.Positions;
         Searching : Boolean;
      begin
         Start_Pos := Index;
         Searching := True;
         Index     := Index + 1; -- The first cheracter is alphanumeric

         while Searching and Index <= E_Strings.Get_Length (E_Str => Input_Line) loop
            if Is_Alphanumeric (Ch => E_Strings.Get_Element (E_Str => Input_Line,
                                                             Pos   => Index)) then
               Index := Index + 1;
            elsif E_Strings.Get_Element (E_Str => Input_Line,
                                         Pos   => Index) = '_' then
               if Index < E_Strings.Get_Length (E_Str => Input_Line)
                 and then Is_Alphanumeric (Ch => E_Strings.Get_Element (E_Str => Input_Line,
                                                                        Pos   => Index + 1)) then
                  Index := Index + 2;
               else
                  Searching := False;
               end if;
            else
               Searching := False;
            end if;
         end loop;

         Token_Rec.Token_Value := E_Strings.Empty_String;
         for I in E_Strings.Positions range Start_Pos .. Index - 1 loop
            E_Strings.Append_Char (E_Str => Token_Rec.Token_Value,
                                   Ch    => E_Strings.Get_Element (E_Str => Input_Line,
                                                                   Pos   => I));
         end loop;

         Token_Rec.Token := Identifier;
      end Get_Identifier;

      procedure Get_Property_List
        (Input_File : in     SPARK_IO.File_Type;
         Anno       : in     Annotations.Anno_Type;
         Input_Line : in out E_Strings.T;
         Index      : in out E_Strings.Positions;
         Token_Rec  :    out Token_Record)
      --# global in     CommandLineData.Content;
      --#        in out SPARK_IO.File_Sys;
      --# derives Index,
      --#         Input_Line,
      --#         SPARK_IO.File_Sys from Anno,
      --#                                Index,
      --#                                Input_File,
      --#                                Input_Line,
      --#                                SPARK_IO.File_Sys &
      --#         Token_Rec         from Anno,
      --#                                CommandLineData.Content,
      --#                                Index,
      --#                                Input_File,
      --#                                Input_Line,
      --#                                SPARK_IO.File_Sys;
      is
         subtype String_1_Range is Integer range 1 .. 1;
         subtype String_1 is String (String_1_Range);
         Anno_Continuation : Boolean;
         Start_String      : E_Strings.T;
      begin
         Token_Rec.Token       := Property_List;
         Token_Rec.Token_Value :=
           E_Strings.Copy_String (Str => String_1'(1 => E_Strings.Get_Element (E_Str => Input_Line,
                                                                               Pos   => Index)));
         Anno_Continuation     := True;
         Index                 := Index + 1;

         loop
            if E_Strings.Get_Length (E_Str => Input_Line) < Index then
               E_Strings.Get_Line (File  => Input_File,
                                   E_Str => Input_Line);
               Index := 1;
               Annotations.Is_Anno_Start (This       => Anno,
                                          Input_Line => Input_Line,
                                          Index      => Index,
                                          OK         => Anno_Continuation);
               if Anno_Continuation then

                  Start_String := E_Strings.Copy_String (Str => "--");
                  E_Strings.Append_Char (E_Str => Start_String,
                                         Ch    => CommandLineData.Content.Anno_Char);
                  E_Strings.Append_Char (E_Str => Start_String,
                                         Ch    => ' ');
                  -- Add symbol to value string to denote coninuation of property_list
                  E_Strings.Append_Examiner_String (E_Str1 => Token_Rec.Token_Value,
                                                    E_Str2 => Start_String);
                  White_Space.Skip (Input_Line => Input_Line,
                                    Index      => Index);
               end if;
            end if;

            exit when not Anno_Continuation
              or else E_Strings.Get_Length (E_Str => Input_Line) < Index
              or else E_Strings.Get_Element (E_Str => Input_Line,
                                             Pos   => Index) = ';';
            E_Strings.Append_Char
              (E_Str => Token_Rec.Token_Value,
               Ch    => E_Strings.Get_Element (E_Str => Input_Line,
                                               Pos   => Index));
            Index := Index + 1;
         end loop;
      end Get_Property_List;

      procedure Extended_Skip_White_Space
        (Input_File    : in     SPARK_IO.File_Type;
         Anno          : in     Annotations.Anno_Type;
         In_Annotation : in out Boolean;
         Input_Line    : in out E_Strings.T;
         Index         : in out E_Strings.Positions)
      --# global in out SPARK_IO.File_Sys;
      --# derives Index,
      --#         Input_Line,
      --#         In_Annotation,
      --#         SPARK_IO.File_Sys from Anno,
      --#                                Index,
      --#                                Input_File,
      --#                                Input_Line,
      --#                                In_Annotation,
      --#                                SPARK_IO.File_Sys;
      is
         In_White_Space      : Boolean;
         Is_Annotation_Start : Boolean;

         procedure Skip_White_Space (Input_Line  : in     E_Strings.T;
                                     Index       : in out E_Strings.Positions;
                                     Still_White :    out Boolean)
         --# derives Index,
         --#         Still_White from Index,
         --#                          Input_Line;
         is
            In_White_Space : Boolean;

            function Is_White_Space (Char : Character) return Boolean is
            begin
               return Char = Ada.Characters.Latin_1.Space or Char = Ada.Characters.Latin_1.HT;
            end Is_White_Space;

         begin
            In_White_Space := True;

            while In_White_Space and Index <= E_Strings.Get_Length (E_Str => Input_Line) loop
               if Is_White_Space (Char => E_Strings.Get_Element (E_Str => Input_Line,
                                                                 Pos   => Index)) then
                  Index := Index + 1;
               else
                  In_White_Space := False;
               end if;
            end loop;

            Still_White := In_White_Space;
         end Skip_White_Space;

      begin
         In_White_Space := True;

         while In_White_Space loop
            if Index > E_Strings.Get_Length (E_Str => Input_Line) then
               if SPARK_IO.End_Of_File (Input_File) then
                  In_White_Space := False;
                  In_Annotation  := False;
               else
                  E_Strings.Get_Line (File  => Input_File,
                                      E_Str => Input_Line);
                  Index := 1;
                  -- Expect an annotation start
                  Annotations.Is_Anno_Start (This       => Anno,
                                             Input_Line => Input_Line,
                                             Index      => Index,
                                             OK         => Is_Annotation_Start);
                  if In_Annotation and not Is_Annotation_Start then
                     In_White_Space := False;
                     In_Annotation  := False;
                  elsif not In_Annotation and Is_Annotation_Start then
                     In_White_Space := False;
                     In_Annotation  := True;
                  else
                     -- Treat as whitespace
                     null;
                  end if;
               end if;
            end if;

            if In_White_Space then
               Skip_White_Space (Input_Line  => Input_Line,
                                 Index       => Index,
                                 Still_White => In_White_Space);
            end if;
         end loop;

         if not In_Annotation then
            Index      := 1;
            Input_Line := E_Strings.Empty_String;
         end if;

      end Extended_Skip_White_Space;

      procedure Check_For_A_Reserved_Word (Tok_Rec : in out Token_Record)
      --# derives Tok_Rec from *;
      is

         procedure Check_For_RW (RWord   : in     String;
                                 Symbol  : in     Token_Type;
                                 Tok_Rec : in out Token_Record)
         --# derives Tok_Rec from *,
         --#                      RWord,
         --#                      Symbol;
         is
         begin
            if E_Strings.Eq1_String (E_Str => Tok_Rec.Token_Value,
                                     Str   => RWord) then
               Tok_Rec.Token       := Symbol;
               Tok_Rec.Token_Value := E_Strings.Copy_String (Str => RWord);
            end if;
         end Check_For_RW;

      begin
         case E_Strings.Get_Element (E_Str => Tok_Rec.Token_Value,
                                     Pos   => 1) is
            when 'd' | 'D' =>
               Check_For_RW (RWord   => "derives",
                             Symbol  => RW_Derives,
                             Tok_Rec => Tok_Rec);
            when 'f' | 'F' =>
               Check_For_RW (RWord   => "from",
                             Symbol  => RW_From,
                             Tok_Rec => Tok_Rec);
            when 'g' | 'G' =>
               Check_For_RW (RWord   => "global",
                             Symbol  => RW_Global,
                             Tok_Rec => Tok_Rec);
            when 'i' | 'I' =>
               if E_Strings.Get_Length (E_Str => Tok_Rec.Token_Value) > 1 then
                  case E_Strings.Get_Element (E_Str => Tok_Rec.Token_Value,
                                              Pos   => 2) is
                     when 'n' | 'N' =>
                        if E_Strings.Get_Length (E_Str => Tok_Rec.Token_Value) > 2 then
                           case E_Strings.Get_Element (E_Str => Tok_Rec.Token_Value,
                                                       Pos   => 3) is
                              when 'h' | 'H' =>
                                 Check_For_RW (RWord   => "inherit",
                                               Symbol  => RW_Inherit,
                                               Tok_Rec => Tok_Rec);
                              when 'i' | 'I' =>
                                 Check_For_RW (RWord   => "initializes",
                                               Symbol  => RW_Initializes,
                                               Tok_Rec => Tok_Rec);
                              when others =>
                                 null;
                           end case;
                        else
                           Check_For_RW (RWord   => "in",
                                         Symbol  => RW_In,
                                         Tok_Rec => Tok_Rec);
                        end if;
                     when 's' | 'S' =>
                        Check_For_RW (RWord   => "is",
                                      Symbol  => RW_Is,
                                      Tok_Rec => Tok_Rec);
                     when others =>
                        null;
                  end case;
               end if;
            when 'm' | 'M' =>
               Check_For_RW (RWord   => "main_program",
                             Symbol  => RW_Main_Program,
                             Tok_Rec => Tok_Rec);
            when 'o' | 'O' =>
               if E_Strings.Get_Length (E_Str => Tok_Rec.Token_Value) > 1 then
                  case E_Strings.Get_Element (E_Str => Tok_Rec.Token_Value,
                                              Pos   => 2) is
                     when 'u' | 'U' =>
                        Check_For_RW (RWord   => "out",
                                      Symbol  => RW_Out,
                                      Tok_Rec => Tok_Rec);
                     when 'w' | 'W' =>
                        Check_For_RW (RWord   => "own",
                                      Symbol  => RW_Own,
                                      Tok_Rec => Tok_Rec);
                     when others =>
                        null;
                  end case;
               end if;
            when 'p' | 'P' =>
               Check_For_RW (RWord   => "protected",
                             Symbol  => RW_Protected,
                             Tok_Rec => Tok_Rec);
            when 't' | 'T' =>
               Check_For_RW (RWord   => "task",
                             Symbol  => RW_Task,
                             Tok_Rec => Tok_Rec);
            when others =>
               null;
         end case;
      end Check_For_A_Reserved_Word;

      procedure Get_Punctuation
        (Input_Line : in     E_Strings.T;
         Index      : in out E_Strings.Positions;
         Token      : in     Token_Type;
         Token_Rec  :    out Token_Record)
      --# derives Index     from * &
      --#         Token_Rec from Index,
      --#                        Input_Line,
      --#                        Token;
      is
         subtype String_1_Range is Integer range 1 .. 1;
         subtype String_1 is String (String_1_Range);
      begin
         Token_Rec.Token       := Token;
         Token_Rec.Token_Value :=
           E_Strings.Copy_String (Str => String_1'(1 => E_Strings.Get_Element (E_Str => Input_Line,
                                                                               Pos   => Index)));
         Index                 := Index + 1;
      end Get_Punctuation;

   begin
      Index             := This.Index;
      Input_File        := This.File;
      Anno              := This.Anno;
      Now_In_Annotation := This.In_Annotation;

      Extended_Skip_White_Space
        (Input_File    => Input_File,
         Anno          => Anno,
         In_Annotation => Now_In_Annotation,
         Input_Line    => This.Line,
         Index         => Index);

      if not Now_In_Annotation then
         This.In_Annotation := False;
         Token_Rec          := Token_Record'(Token       => Annotation_End,
                                             Token_Value => E_Strings.Empty_String);
      elsif not This.In_Annotation then
         This.In_Annotation := True;
         Token_Rec          := Token_Record'(Token       => Annotation_Start,
                                             Token_Value => E_Strings.Empty_String);
      else
         if Is_Alphanumeric (Ch => E_Strings.Get_Element (E_Str => This.Line,
                                                          Pos   => Index)) then
            Get_Identifier (Input_Line => This.Line,
                            Index      => Index,
                            Token_Rec  => Token_Rec);
            Check_For_A_Reserved_Word (Tok_Rec => Token_Rec);
         else
            case E_Strings.Get_Element (E_Str => This.Line,
                                        Pos   => Index) is
               when ':' =>
                  Get_Punctuation (Input_Line => This.Line,
                                   Index      => Index,
                                   Token      => Colon,
                                   Token_Rec  => Token_Rec);
               when ',' =>
                  Get_Punctuation (Input_Line => This.Line,
                                   Index      => Index,
                                   Token      => Comma,
                                   Token_Rec  => Token_Rec);
               when '(' =>
                  Get_Property_List
                    (Input_File => Input_File,
                     Anno       => Anno,
                     Input_Line => This.Line,
                     Index      => Index,
                     Token_Rec  => Token_Rec);
               when '.' =>
                  Get_Punctuation (Input_Line => This.Line,
                                   Index      => Index,
                                   Token      => Point,
                                   Token_Rec  => Token_Rec);
               when ';' =>
                  Get_Punctuation (Input_Line => This.Line,
                                   Index      => Index,
                                   Token      => Semicolon,
                                   Token_Rec  => Token_Rec);
               when others =>
                  Get_Punctuation (Input_Line => This.Line,
                                   Index      => Index,
                                   Token      => Other_Punct,
                                   Token_Rec  => Token_Rec);
            end case;
         end if;
      end if;

      This.Index := Index;
   end Next;

   function Get_Col_No (This : State) return E_Strings.Positions is
   begin
      return This.Index;
   end Get_Col_No;

end SimpleLex;
