-------------------------------------------------------------------------------
-- (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.Lex)
procedure GetNumber (Curr_Line : in out Line_Context;
                     Token     :    out SP_Symbols.SP_Terminal)
-- Given that the character at the current position in the line buffer is a
-- digit GetNum recognises the maximal length subsequence from the line buffer
-- defined by one of the following regular expressions :-
--    integer_number ::= integer [exponent]
--    real_number    ::= integer.integer [exponent]
--    based_integer  ::= integer#based_integer# [exponent]
--    based_real     ::= integer#based_integer.based_integer# [exponent]
--    illegal_number ::= numeric_literal non_number_separator {non_number_separator}
-- where
--    non_number_separator is any chahracter which cannot legally separate
--    a numeric_literal from the next token in the Ada lexis.
-- On exit Token is set according to which of  the expressions is recognised.
-- The current position of the line buffer is the character immediately
-- following the recognised subsequence.
is
   Ch           : Character;
   Legal        : Boolean;
   Token_So_Far : SP_Symbols.SP_Terminal;

   function Num_Sep (Ch : Character) return Boolean is
   begin
      return Ch = ' ' or else Ch = End_Of_Text or else Simple_Delimiter (Ch => Ch) or else Format_Effector (Ch => Ch);
   end Num_Sep;

   function Extended_Digit (Ch : Character) return Boolean is
   begin
      -- We consider an extended digit to be a decimal digit or
      -- any letter, so...
      return Letter_Or_Digit (Ch => Ch);
   end Extended_Digit;

   procedure Get_Integer (Curr_Line : in out Line_Context;
                          Legal     :    out Boolean)
   --# derives Curr_Line,
   --#         Legal     from Curr_Line;
   --# pre E_Strings.Get_Length (Curr_Line.Conts) < Natural'Last and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1;
   --# post E_Strings.Get_Length (Curr_Line.Conts) =  E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos >= Curr_Line~.Curr_Pos and
   --#   (Curr_Line~.Curr_Pos <= E_Strings.Get_Length (Curr_Line~.Conts) -> (Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos)) and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos;
   --
   -- This procedure recognises the maximal length sequence from the
   -- line buffer satisfying one of the two following regular expressions
   --    integer     ::= digit {[underline] digit}
   --    illegal_int ::= integer underline
   --
   -- If an integer is recognised, then Legal is set to TRUE otherwise False.
   --
   is
      type Num_State is (Underline_Or_Digit, Digit_Only, Illegal_Num_State);
      State : Num_State;
      Ch    : Character;
   begin
      LineManager.Accept_Char (Curr_Line => Curr_Line); -- First digit already recognised.
      State := Underline_Or_Digit;
      loop
         --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
         --#   Curr_Line.Curr_Pos >= Curr_Line~.Curr_Pos and
         --#   (Curr_Line~.Curr_Pos <= E_Strings.Get_Length (Curr_Line~.Conts) -> (Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos)) and
         --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
         --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
         --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos;
         Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                      Pos   => Curr_Line.Curr_Pos);
         case State is
            when Underline_Or_Digit =>
               if Ch = '_' then
                  State := Digit_Only;
               end if;
            when Digit_Only =>
               if Ada.Characters.Handling.Is_Digit (Ch) then
                  State := Underline_Or_Digit;
               else
                  State := Illegal_Num_State;
               end if;
            when Illegal_Num_State =>
               null;
         end case;
         exit when (not Ada.Characters.Handling.Is_Digit (Ch) and then Ch /= '_') or else State = Illegal_Num_State;
         LineManager.Accept_Char (Curr_Line => Curr_Line);
      end loop;

      Legal := (State = Underline_Or_Digit);
   end Get_Integer;

   procedure Get_Based_Integer (Curr_Line : in out Line_Context;
                                Legal     :    out Boolean)
   --# derives Curr_Line,
   --#         Legal     from Curr_Line;
   --# pre E_Strings.Get_Length (Curr_Line.Conts) < Natural'Last and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1;
   --# post E_Strings.Get_Length (Curr_Line.Conts) =  E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos >= Curr_Line~.Curr_Pos and
   --#   (Curr_Line~.Curr_Pos <= E_Strings.Get_Length (Curr_Line~.Conts) -> (Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos)) and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos;
   --
   -- This procedure recognises the maximal length sequence from the
   -- line buffer satisfying on of the the two following regular expression
   --    based_integer ::= extended_digit {[underline] extended_digit}
   --    illegal_based ::= based_integer underline
   --
   -- If a based_integer is recognised, then Legal is set to TRUE otherwise False.
   --
   is
      type Num_State is (Underline_Or_Ext_Digit, Ext_Digit_Only, Illegal_Num_State);
      State : Num_State;
      Ch    : Character;
   begin
      LineManager.Accept_Char (Curr_Line => Curr_Line); -- First extended digit already recognised.
      State := Underline_Or_Ext_Digit;
      loop
         --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
         --#   Curr_Line.Curr_Pos >= Curr_Line~.Curr_Pos and
         --#   (Curr_Line~.Curr_Pos <= E_Strings.Get_Length (Curr_Line~.Conts) -> (Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos)) and
         --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
         --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
         --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos;
         Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                      Pos   => Curr_Line.Curr_Pos);
         case State is
            when Underline_Or_Ext_Digit =>
               if Ch = '_' then
                  State := Ext_Digit_Only;
               end if;
            when Ext_Digit_Only =>
               if Extended_Digit (Ch => Ch) then
                  State := Underline_Or_Ext_Digit;
               else
                  State := Illegal_Num_State;
               end if;
            when Illegal_Num_State =>
               null;
         end case;
         exit when (not Extended_Digit (Ch => Ch) and then Ch /= '_') or else State = Illegal_Num_State;
         LineManager.Accept_Char (Curr_Line => Curr_Line);
      end loop;

      Legal := (State = Underline_Or_Ext_Digit);
   end Get_Based_Integer;

begin
   Get_Integer (Curr_Line => Curr_Line,
                Legal     => Legal);

   if Legal then
      Token_So_Far := SP_Symbols.integer_number;
   else
      Token_So_Far := SP_Symbols.illegal_number;
   end if;

   --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos and
   --#   Token_So_Far in SP_Symbols.SP_Terminal and
   --#   Token_So_Far /= SP_Symbols.annotation_end and Token_So_Far /= SP_Symbols.SPEND;

   if E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                             Pos   => Curr_Line.Curr_Pos) = '#' then
      LineManager.Accept_Char (Curr_Line => Curr_Line);
      if Extended_Digit (Ch => E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                                      Pos   => Curr_Line.Curr_Pos)) then
         Get_Based_Integer (Curr_Line => Curr_Line,
                            Legal     => Legal);
         if Legal and then Token_So_Far /= SP_Symbols.illegal_number then
            Token_So_Far := SP_Symbols.based_integer;
         else
            Token_So_Far := SP_Symbols.illegal_number;
         end if;
      end if;
   end if;

   --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos and
   --#   Token_So_Far in SP_Symbols.SP_Terminal and
   --#   Token_So_Far /= SP_Symbols.annotation_end and Token_So_Far /= SP_Symbols.SPEND;

   if E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                             Pos   => Curr_Line.Curr_Pos) = '.' then
      LineManager.Lookahead_Char (Curr_Line => Curr_Line,
                                  Ch        => Ch); -- Check for '..' symbol.
      LineManager.Reject_Lookahead (Curr_Line => Curr_Line); -- Current and lookahead position at first '.' char.
      if Token_So_Far = SP_Symbols.integer_number then
         if Ada.Characters.Handling.Is_Digit (Ch) then
            LineManager.Accept_Char (Curr_Line => Curr_Line); -- Accept decimal point, '.', char.
            Get_Integer (Curr_Line => Curr_Line,
                         Legal     => Legal);
            if Legal then
               Token_So_Far := SP_Symbols.real_number;
            else
               Token_So_Far := SP_Symbols.illegal_number;
            end if;
         end if;
      elsif Token_So_Far = SP_Symbols.based_integer then
         if Extended_Digit (Ch => Ch) then
            LineManager.Accept_Char (Curr_Line => Curr_Line); -- Accept decimal point, '.', char.
            Get_Based_Integer (Curr_Line => Curr_Line,
                               Legal     => Legal);
            if Legal then
               Token_So_Far := SP_Symbols.based_real;
            else
               Token_So_Far := SP_Symbols.illegal_number;
            end if;
         end if;
      end if;
   end if;

   --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos and
   --#   Token_So_Far in SP_Symbols.SP_Terminal and
   --#   Token_So_Far /= SP_Symbols.annotation_end and Token_So_Far /= SP_Symbols.SPEND;

   if Token_So_Far = SP_Symbols.based_integer or else Token_So_Far = SP_Symbols.based_real then
      if E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                Pos   => Curr_Line.Curr_Pos) = '#' then
         LineManager.Accept_Char (Curr_Line => Curr_Line);
      else
         Token_So_Far := SP_Symbols.illegal_number;
      end if;
   end if;

   --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos and
   --#   Token_So_Far in SP_Symbols.SP_Terminal and
   --#   Token_So_Far /= SP_Symbols.annotation_end and Token_So_Far /= SP_Symbols.SPEND;

   Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                Pos   => Curr_Line.Curr_Pos);
   if Ch = 'E' or else Ch = 'e' then
      LineManager.Accept_Char (Curr_Line => Curr_Line);
      Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                   Pos   => Curr_Line.Curr_Pos);
      if Ch = '+' then
         LineManager.Accept_Char (Curr_Line => Curr_Line);
         Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                      Pos   => Curr_Line.Curr_Pos);
      elsif Ch = '-' then
         LineManager.Accept_Char (Curr_Line => Curr_Line);
         Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                      Pos   => Curr_Line.Curr_Pos);
         if Token_So_Far /= SP_Symbols.real_number and then Token_So_Far /= SP_Symbols.based_real then
            Token_So_Far := SP_Symbols.illegal_number;
         end if;
      end if;
      if Ada.Characters.Handling.Is_Digit (Ch) then
         Get_Integer (Curr_Line => Curr_Line,
                      Legal     => Legal);
         if not Legal then
            Token_So_Far := SP_Symbols.illegal_number;
         end if;
      else
         Token_So_Far := SP_Symbols.illegal_number;
      end if;
   end if;

   --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
   --#   Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos and
   --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
   --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
   --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos and
   --#   Token_So_Far in SP_Symbols.SP_Terminal and
   --#   Token_So_Far /= SP_Symbols.annotation_end and Token_So_Far /= SP_Symbols.SPEND;

   Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                Pos   => Curr_Line.Curr_Pos);
   if Num_Sep (Ch => Ch) then
      Token := Token_So_Far;
   else
      while not Num_Sep (Ch => Ch) loop
         --# assert E_Strings.Get_Length (Curr_Line.Conts) = E_Strings.Get_Length (Curr_Line~.Conts) and
         --#   Curr_Line.Curr_Pos > Curr_Line~.Curr_Pos and
         --#   Curr_Line.Curr_Pos <= E_Strings.Get_Length (Curr_Line.Conts) + 1 and
         --#   Curr_Line.Lookahead_Pos = Curr_Line.Curr_Pos and
         --#   Curr_Line.Last_Token_Pos = Curr_Line~.Last_Token_Pos and
         --#   Token_So_Far in SP_Symbols.SP_Terminal and
         --#   Token_So_Far /= SP_Symbols.annotation_end and Token_So_Far /= SP_Symbols.SPEND;
         LineManager.Accept_Char (Curr_Line => Curr_Line);
         Ch := E_Strings.Get_Element (E_Str => Curr_Line.Conts,
                                      Pos   => Curr_Line.Curr_Pos);
      end loop;
      Token := SP_Symbols.illegal_number;
   end if;

end GetNumber;
