File : reverse_line_tokens.adb


------------------------------------------------------------------------------
--  Demonstration code to read a line, split it into tokens, and reverse them.
------------------------------------------------------------------------------
--  This version is the Ada way of programming but likely fail to process huge
--  amounts of data due to stack overflow.                                    
------------------------------------------------------------------------------

with Ada.Text_IO; use Ada.Text_IO;
with Ada.Strings.Maps, Ada.Strings.Fixed; use Ada.Strings;

procedure reverse_line_tokens is
   type Token is record
      first, last : Positive;    --  Slice marker of an external string
   end record;
   
   --  The list of all tokens is an simple array of all tokens.
   --  It's not possible to hold an array of Strings, because Strings are
   --  unconstrained.
   type Tokens is array (Positive range <>) of Token;
   
   --  The empty array required for recursion termination.
   --  Even the array does not contain any elements, Ada requires a dummy
   --  definition structure.
   no_tokens : constant Tokens (1 .. 0) := (others => (1, 1));
   
   --  Read the whole line from standard input into a single string.
   --  Note, that the String size is returned, too!  You do not need
   --  to guess how large the array might be.
   function get_whole_line return String;
   
   --  Split a string into a array of tokens. Note, that the array size is
   --  returned, too! You do not need to guess how large the array might be.
   function split (line : String) return Tokens;

   --  Implementation of get_whole_line.
   --  A chunk of Characters is read at once from the current line.
   --  Get_Line uses two out parameters, but all constrained parameters are
   --  always "in" parameter for there own constraints. So the Get_Line
   --  procedure learns about the size of the chunk. Because the constraints
   --  als always "in", they can't be changed. Get_Line returns the really
   --  filled amount as a second out parameter, and fills the chunk partially.
   --  If the line continues, the function is called recursivly to get the
   --  rest of the line and than concatenate both results.
   function get_whole_line return String is
      buff : String (1 .. 100);
      last : Natural;
   begin
      Get_Line (buff, last);
      if last < buff'Last then
         return buff (buff'First .. last);
      else
         return buff & get_whole_line;
      end if;
   end get_whole_line;

   --  Implementation of split.
   --  Scans the string for tokens using Ada.Strings.Fixed.Find_Token.
   --  The out parameter "last" is used a the iteration variable.
   --  If a token was found, collect the rest by a recursive call and
   --  combine those results together.
   function split (line : String) return Tokens is
      first : Positive;
      last  : Natural;
   begin
      Fixed.Find_Token (
        Source => line,
        Set    => Maps.To_Set (" " & ASCII.HT & ASCII.LF),
        Test   => Outside,
        First  => first,
        Last   => last
      );
      if last = 0 then
         return no_tokens;
      else
         return Token'(first, last) & split (line (last + 1 .. line'Last));
      end if;
   end split;
   
   --  Start real work here.
begin
   loop
      declare
         --  Read in the whole line into a variable which should be immutable.
         --  The required size (constraint) comes from the defining value.
         line : constant String := get_whole_line;
         
         --  Split the whole text into an array of tokens.
         --  The required size (constraint) comes from the defining value.
         toks : constant Tokens := split (line);
      begin
         --  Stop endless loop at predefined input.
         exit when line = "quit";
         
         for i in reverse toks'Range loop
            --  Output a token slice.
            Put (line (toks (i).first .. toks (i).last));
            if i /= toks'First then --  prevent a trailing one.
               Put (' ');
            end if;
         end loop;
         New_Line;
      end;
   end loop;
   
   --  Graceful exit.
   Put_Line ("Sie haben ein einfaches Programm sehr glücklich gemacht.");

exception
   --  Ungraceful exit, EOF detected.
   when End_Error =>
      Put_Line ("Sie haben ein einfaches Programm sehr unglücklich gemacht.");
end reverse_line_tokens;