-------------------------------------------------------------------------------
--
--  <STRONG>Copyright &copy; 2001, 2002 by Thomas Wolf.</STRONG>
--  <BLOCKQUOTE>
--    This piece of software is free software; you can redistribute it and/or
--    modify it under the terms of the  GNU General Public License as published
--    by the Free Software  Foundation; either version 2, or (at your option)
--    any later version. This software is distributed in the hope that it will
--    be useful, but <EM>without any warranty</EM>; without even the implied
--    warranty of <EM>merchantability or fitness for a particular purpose.</EM>
--    See the GNU General Public License for  more details. You should have
--    received a copy of the GNU General Public License with this distribution,
--    see file "<A HREF="GPL.txt">GPL.txt</A>". If not, write to the Free
--    Software Foundation, 59 Temple Place - Suite 330, Boston, MA 02111-1307,
--    USA.
--  </BLOCKQUOTE>
--  <BLOCKQUOTE>
--    As a special exception from the GPL, if other files instantiate generics
--    from this unit, or you link this unit with other files to produce an
--    executable, this unit does not by itself cause the resulting executable
--    to be covered by the GPL. This exception does not however invalidate any
--    other reasons why the executable file might be covered by the GPL.
--  </BLOCKQUOTE>
--
--  <AUTHOR>
--    Thomas Wolf  (TW) <E_MAIL>
--  </AUTHOR>
--
--  <PURPOSE>
--    A complete replacement for @Ada.Strings.Unbounded@, with conversion
--    routines. The problem with the standard unbounded strings package is
--    that it lacks certain operations (such as I/O operations, or a way to
--    append a slice of an @Unbounded_String@ to another @Unbounded_String@
--    without having an intermediary conversion to @String@).
--
--    This implementation is fully portable; it doesn't use any GNAT-specific
--    pragmas like @Stream_Convert@. The price to pay is that this package
--    cannot be preelaborated (since @Null_Unbounded_String@ is a constant of
--    a controlled type). I wonder how the language designers did envision that
--    this be done...
--
--    @Unbounded_String@s are streamable; however, the format is not compatible
--    with the one used by GNAT's implementation of @Ada.Strings.Unbounded@.
--  </PURPOSE>
--
--  <NOT_TASK_SAFE>
--
--  <STORAGE>
--    Dynamic storage allocation in the default pool.
--  </STORAGE>
--
--  <HISTORY>
--    07-JUN-2002   TW  Initial version.
--    27-JUN-2002   TW  Added two 'Find_Token' variants: one with a single
--                      'From' parameter, and one with 'Low' and 'High'
--                      parameters.
--  </HISTORY>
-------------------------------------------------------------------------------

pragma License (Modified_GPL);

with Ada.Strings.Fixed;

with Ada.Unchecked_Deallocation;

with Util.Strings;

package body Util.Text is

   --  Implementation note: our string data is always indexed from 1. We
   --  therefore can use 'Last instead of 'Length, which is slightly faster
   --  because 'Length must be calculated as
   --
   --  'Length := 'Last - 'First + 1;
   --  if 'Length < 0 then 'Length := 0; end if;

   package ASF renames Ada.Strings.Fixed;
   package ASM renames Ada.Strings.Maps;
   package ASU renames Ada.Strings.Unbounded;

   use Util.Strings;

   pragma Suppress (Access_Check);
   --  No null-pointer checking: we *know* that the 'Data' field of an
   --  Unbounded_String can never be null.

   ----------------------------------------------------------------------------

   Index_Error   : exception renames Ada.Strings.Index_Error;
   Pattern_Error : exception renames Ada.Strings.Pattern_Error;

   ----------------------------------------------------------------------------

   procedure Deallocate is
      new Ada.Unchecked_Deallocation (String, String_Access);

   procedure Free
     (Ptr : in out String_Access)
   is
   begin
      if Ptr /= Null_String'Access then
         Deallocate (Ptr);
      end if;
      Ptr := null;
   end Free;

   ----------------------------------------------------------------------------
   --  Utility operations

   procedure Slice_Check
     (Low, High, Length : in Natural);
   pragma Inline (Slice_Check);

   procedure Slice_Check
     (Low, High, Length : in Natural)
   is
   begin
      if Low > Length + 1 or else High > Length then
         --  Checking 'High' is ok by AI95-00128.
         raise Index_Error;
      end if;
   end Slice_Check;

   procedure Trim
     (Source : in     String;
      Side   : in     Ada.Strings.Trim_End;
      I, J   :    out Natural)
   is
      use type Ada.Strings.Trim_End;
   begin
      I := Source'First;
      J := Source'Last;
      if Side /= Ada.Strings.Right then
         while I <= J and then Source (I) = ' ' loop
            I := I + 1;
         end loop;
      end if;
      if Side /= Ada.Strings.Left then
         while J >= I and then Source (J) = ' ' loop
            J := J - 1;
         end loop;
      end if;
   end Trim;

   procedure Trim
     (Source : in     String;
      Left   : in     ASM.Character_Set;
      Right  : in     ASM.Character_Set;
      I, J   :    out Natural)
   is
      use type ASM.Character_Set;
   begin
      I := Source'First;
      J := Source'Last;
      if Left /= ASM.Null_Set then
         while I <= J and then ASM.Is_In (Source (I), Left) loop
            I := I + 1;
         end loop;
      end if;
      if Right /= ASM.Null_Set then
         while J >= I and then ASM.Is_In (Source (J), Right) loop
            J := J - 1;
         end loop;
      end if;
   end Trim;

   function Slice
     (Source : in String_Access;
      Low    : in Positive;
      High   : in Natural)
     return String_Access
   is
   begin
      if Low > High then return Null_String'Access; end if;
      declare
         Result : constant String_Access := new String (1 .. High - Low + 1);
      begin
         Result.all := Source (Low .. High);
         return Result;
      end;
   end Slice;

   function Insert
     (Source : in String_Access;
      Before : in Positive;
      Item   : in String)
     return String_Access
   is
      Length : constant Natural       := Item'Length;
      Ptr    : constant String_Access :=
        new String (1 .. Source'Last + Length);
   begin
      Ptr (1 .. Before - 1)               := Source (1 .. Before - 1);
      Ptr (Before .. Before + Length - 1) := Item;
      Ptr (Before + Length .. Ptr'Last)   :=
        Source (Before .. Source'Last);
      return Ptr;
   end Insert;

   function Replace_Slice
     (Source : in String_Access;
      Low    : in Positive;
      High   : in Natural;
      By     : in String)
     return String_Access
   is
   begin
      if High >= Low then
         declare
            Suffix_Length : constant Integer :=
              Integer'Max (0, Source'Last - High);
            By_Length     : constant Natural := By'Length;
            New_Length    : constant Integer :=
              Low - 1 + By_Length + Suffix_Length;
            Ptr : constant String_Access := new String (1 .. New_Length);
         begin
            Ptr (1 .. Low - 1)                := Source (1 .. Low - 1);
            Ptr (Low .. Low + By_Length - 1)  := By;
            Ptr (Low + By_Length .. Ptr'Last) :=
              Source (High + 1 .. Source'Last);
            return Ptr;
         end;
      else
         return Insert (Source, Low, By);
      end if;
   end Replace_Slice;

   ----------------------------------------------------------------------------
   --  Public operations, in alphabetic order; operators first.

   function "&"
     (Left, Right : Unbounded_String)
     return Unbounded_String
   is
      Result : Unbounded_String :=
        (Ada.Finalization.Controlled with
           Data => new String (1 .. Left.Data'Last + Right.Data'Last));
   begin
      Result.Data (1 .. Left.Data'Last)                    := Left.Data.all;
      Result.Data (Left.Data'Last + 1 .. Result.Data'Last) := Right.Data.all;
      return Result;
   end "&";

   function "&"
     (Left  : in Unbounded_String;
      Right : in String)
     return Unbounded_String
   is
      Length : constant Natural := Right'Length;
      Result : Unbounded_String :=
        (Ada.Finalization.Controlled with
           Data => new String (1 .. Left.Data'Last + Length));
   begin
      Result.Data (1 .. Left.Data'Last)                    := Left.Data.all;
      Result.Data (Left.Data'Last + 1 .. Result.Data'Last) := Right;
      return Result;
   end "&";

   function "&"
     (Left  : in String;
      Right : in Unbounded_String)
     return Unbounded_String
   is
      Length : constant Natural := Left'Length;
      Result : Unbounded_String :=
        (Ada.Finalization.Controlled with
           Data => new String (1 .. Length + Right.Data'Last));
   begin
      Result.Data (1 .. Length)                    := Left;
      Result.Data (Length + 1 .. Result.Data'Last) := Right.Data.all;
      return Result;
   end "&";

   function "&"
     (Left  : in Unbounded_String;
      Right : in Character)
     return Unbounded_String
   is
      Result : Unbounded_String :=
        (Ada.Finalization.Controlled with
           Data => new String (1 .. Left.Data'Last + 1));
   begin
      Result.Data (1 .. Left.Data'Last) := Left.Data.all;
      Result.Data (Left.Data'Last + 1)  := Right;
      return Result;
   end "&";

   function "&"
     (Left  : in Character;
      Right : in Unbounded_String)
     return Unbounded_String
   is
      Result : Unbounded_String :=
        (Ada.Finalization.Controlled with
           Data => new String (1 .. Right.Data'Last + 1));
   begin
      Result.Data (1)                     := Left;
      Result.Data (2 .. Result.Data'Last) := Right.Data.all;
      return Result;
   end "&";

   function "="
     (Left, Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left.Data.all = Right.Data.all;
   end "=";

   function "="
     (Left  : in Unbounded_String;
      Right : in String)
     return Boolean
   is
   begin
      return Left.Data.all = Right;
   end "=";

   function "="
     (Left  : in String;
      Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left = Right.Data.all;
   end "=";

   function "<"
     (Left, Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left.Data.all < Right.Data.all;
   end "<";

   function "<"
     (Left  : in Unbounded_String;
      Right : in String)
     return  Boolean
   is
   begin
      return Left.Data.all < Right;
   end "<";

   function "<"
     (Left  : in String;
      Right : in Unbounded_String)
     return  Boolean
   is
   begin
      return Left < Right.Data.all;
   end "<";

   function "<="
     (Left, Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left.Data.all <= Right.Data.all;
   end "<=";

   function "<="
     (Left  : in Unbounded_String;
      Right : in String)
     return Boolean
   is
   begin
      return Left.Data.all <= Right;
   end "<=";

   function "<="
     (Left  : in String;
      Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left <= Right.Data.all;
   end "<=";

   function ">"
     (Left, Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left.Data.all > Right.Data.all;
   end ">";

   function ">"
     (Left  : in Unbounded_String;
      Right : in String)
     return  Boolean
   is
   begin
      return Left.Data.all > Right;
   end ">";

   function ">"
     (Left  : in String;
      Right : in Unbounded_String)
     return  Boolean
   is
   begin
      return Left > Right.Data.all;
   end ">";

   function ">="
     (Left, Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left.Data.all >= Right.Data.all;
   end ">=";

   function ">="
     (Left  : in Unbounded_String;
      Right : in String)
     return Boolean
   is
   begin
      return Left.Data.all >= Right;
   end ">=";

   function ">="
     (Left  : in String;
      Right : in Unbounded_String)
     return Boolean
   is
   begin
      return Left >= Right.Data.all;
   end ">=";

   function "*"
     (Left  : in Natural;
      Right : in Character)
     return Unbounded_String
   is
      New_String : Unbounded_String :=
        (Ada.Finalization.Controlled with Data => new String (1 .. Left));
   begin
      for I in New_String.Data'Range loop
         New_String.Data (I) := Right;
      end loop;
      return New_String;
   end "*";

   function "*"
     (Left  : in Natural;
      Right : in String)
     return Unbounded_String
   is
   begin
      if Left = 0 or else Right'Last < Right'First then
         return Null_Unbounded_String;
      end if;
      declare
         Length     : constant Natural := Right'Last - Right'First + 1;
         New_String : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (1 .. Left * Length));
      begin
         for I in 1 .. Left loop
            New_String.Data (I * Length - Length + 1 .. I * Length) := Right;
         end loop;
         return New_String;
      end;
   end "*";

   function "*"
     (Left  : in Natural;
      Right : in Unbounded_String)
     return Unbounded_String
   is
   begin
      return Left * Right.Data.all;
   end "*";

   ----------------------------------------------------------------------------
   --  Append.

   --  Standard
   procedure Append
     (Source   : in out Unbounded_String;
      New_Item : in     Unbounded_String)
   is
   begin
      Append (Source, New_Item.Data.all);
   end Append;

   --  Standard
   procedure Append
     (Source   : in out Unbounded_String;
      New_Item : in     String)
   is
   begin
      if New_Item'Last < New_Item'First then return; end if;
      declare
         Ptr : constant String_Access :=
           new String (1 .. Source.Data'Last + New_Item'Length);
      begin
         Ptr (1 .. Source.Data'Last)            := Source.Data.all;
         Ptr (Source.Data'Last + 1 .. Ptr'Last) := New_Item;
         Free (Source.Data);
         Source.Data := Ptr;
      end;
   end Append;

   --  Standard
   procedure Append
     (Source   : in out Unbounded_String;
      New_Item : in     Character)
   is
      Ptr : constant String_Access := new String (1 .. Source.Data'Last + 1);
   begin
      Ptr (1 .. Source.Data'Last) := Source.Data.all;
      Ptr (Source.Data'Last + 1)  := New_Item;
      Free (Source.Data);
      Source.Data := Ptr;
   end Append;

   --  Slice
   function  Append
     (Source : in Unbounded_String;
      From   : in Unbounded_String;
      Low    : in Positive;
      High   : in Natural)
     return Unbounded_String
   is
   begin
      Slice_Check (Low, High, From.Data'Last);
      if Low <= High then
         return Source & From.Data (Low .. High);
      else
         return Source;
      end if;
   end Append;

   --  Slice
   procedure Append
     (Source : in out Unbounded_String;
      From   : in     Unbounded_String;
      Low    : in     Positive;
      High   : in     Natural)
   is
   begin
      Slice_Check (Low, High, From.Data'Last);
      if Low <= High then
         Append (Source, From.Data (Low .. High));
      end if;
   end Append;

   ----------------------------------------------------------------------------
   --  Count.

   --  Standard
   function Count
     (Source  : in Unbounded_String;
      Pattern : in String;
      Mapping : in Ada.Strings.Maps.Character_Mapping :=
                     Ada.Strings.Maps.Identity)
     return Natural
   is
      use type ASM.Character_Mapping;
   begin
      if Mapping = Ada.Strings.Maps.Identity then
         return Count (Source.Data.all, Pattern);
      elsif Pattern'Last < Pattern'First then
         raise Pattern_Error;
      elsif Pattern'Last - Pattern'First + 1 > Source.Data'Last then
         return 0;
      end if;
      declare
         Temporary : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (Source.Data'Range));
      begin
         for I in Temporary.Data'Range loop
            Temporary.Data (I) := ASM.Value (Mapping, Source.Data (I));
         end loop;
         return Count (Temporary.Data.all, Pattern);
      end;
   end Count;

   --  Standard
   function Count
     (Source   : in Unbounded_String;
      Pattern  : in String;
      Mapping  : in Ada.Strings.Maps.Character_Mapping_Function)
     return Natural
   is
      use type ASM.Character_Mapping_Function;
   begin
      if Pattern'Last < Pattern'First then
         raise Pattern_Error;
      elsif Pattern'Last - Pattern'First + 1 > Source.Data'Last then
         return 0;
      elsif Mapping = null then
         raise Constraint_Error;
      end if;
      declare
         Temporary : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (Source.Data'Range));
      begin
         for I in Temporary.Data'Range loop
            Temporary.Data (I) := Mapping (Source.Data (I));
         end loop;
         return Count (Temporary.Data.all, Pattern);
      end;
   end Count;

   --  Standard
   function Count
     (Source : in Unbounded_String;
      Set    : in Ada.Strings.Maps.Character_Set)
     return Natural
   is
   begin
      return ASF.Count (Source.Data.all, Set);
   end Count;

   --  Unbounded
   function Count
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      Mapping : in Ada.Strings.Maps.Character_Mapping :=
                     Ada.Strings.Maps.Identity)
     return Natural
   is
   begin
      return Count (Source, Pattern.Data.all, Mapping);
   end Count;

   --  Unbounded
   function Count
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Natural
   is
   begin
      return Count (Source, Pattern.Data.all, Mapping);
   end Count;

   ----------------------------------------------------------------------------
   --  Delete.

   --  Standard
   function Delete
     (Source  : in Unbounded_String;
      From    : in Positive;
      Through : in Natural)
     return Unbounded_String
   is
   begin
      Slice_Check (From, Through, Source.Data'Last);
      if From > Through then return Source; end if;
      declare
         New_String : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data =>  new String (1 ..
                                   Source.Data'Last - (Through - From + 1)));
      begin
         New_String.Data (1 .. From - 1) := Source.Data (1 .. From - 1);
         New_String.Data (From .. New_String.Data'Last) :=
           Source.Data (Through + 1 .. Source.Data'Last);
         return New_String;
      end;
   end Delete;

   --  Standard
   procedure Delete
     (Source  : in out Unbounded_String;
      From    : in     Positive;
      Through : in     Natural)
   is
   begin
      Slice_Check (From, Through, Source.Data'Last);
      if From > Through then return; end if;
      declare
         New_String : constant String_Access :=
           new String (1 .. Source.Data'Last - (Through - From + 1));
      begin
         New_String (1 .. From - 1) := Source.Data (1 .. From - 1);
         New_String (From .. New_String'Last) :=
           Source.Data (Through + 1 .. Source.Data'Last);
         Free (Source.Data);
         Source.Data := New_String;
      end;
   end Delete;

   ----------------------------------------------------------------------------
   --  Find.

   --  Standard
   function Element
     (Source : in Unbounded_String;
      Index  : in Positive)
     return Character
   is
   begin
      return Source.Data (Index);
   end Element;

   ----------------------------------------------------------------------------
   --  Find.

   --  New
   function Find
     (Source  : in Unbounded_String;
      Pattern : in Character;
      Going   : in Ada.Strings.Direction := Ada.Strings.Forward)
     return Natural
   is
      use type Ada.Strings.Direction;
   begin
      if Going = Forward then
         return First_Index (Source.Data.all, Pattern);
      else
         return Last_Index (Source.Data.all, Pattern);
      end if;
   end Find;

   --  New
   function Find
     (Source  : in Unbounded_String;
      Pattern : in String;
      Going   : in Ada.Strings.Direction := Ada.Strings.Forward)
     return Natural
   is
      use type Ada.Strings.Direction;
   begin
      if Going = Forward then
         return First_Index (Source.Data.all, Pattern);
      else
         return Last_Index (Source.Data.all, Pattern);
      end if;
   end Find;

   --  New
   function Find
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      Going   : in Ada.Strings.Direction  := Ada.Strings.Forward)
     return Natural
   is
      use type Ada.Strings.Direction;
   begin
      if Going = Forward then
         return First_Index (Source.Data.all, Pattern.Data.all);
      else
         return Last_Index (Source.Data.all, Pattern.Data.all);
      end if;
   end Find;

   --  New, From
   function Find
     (Source  : in Unbounded_String;
      Pattern : in Character;
      From    : in Positive;
      Going   : in Ada.Strings.Direction := Ada.Strings.Forward)
     return Natural
   is
      use type Ada.Strings.Direction;
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      if Going = Forward then
         return First_Index (Source.Data (From .. Source.Data'Last), Pattern);
      else
         return Last_Index (Source.Data (1 .. From), Pattern);
      end if;
   end Find;

   --  New, From
   function Find
     (Source  : in Unbounded_String;
      Pattern : in String;
      From    : in Positive;
      Going   : in Ada.Strings.Direction := Ada.Strings.Forward)
     return Natural
   is
      use type Ada.Strings.Direction;
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      if Going = Forward then
         return First_Index (Source.Data (From .. Source.Data'Last), Pattern);
      else
         return Last_Index (Source.Data (1 .. From), Pattern);
      end if;
   end Find;

   --  New, From
   function Find
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      From    : in Positive;
      Going   : in Ada.Strings.Direction  := Ada.Strings.Forward)
     return Natural
   is
   begin
      return Find (Source, Pattern.Data.all, From, Going);
   end Find;

   ----------------------------------------------------------------------------
   --  Find_Token.

   --  Standard
   procedure Find_Token
     (Source : in     Unbounded_String;
      Set    : in     Ada.Strings.Maps.Character_Set;
      Test   : in     Ada.Strings.Membership;
      First  :    out Positive;
      Last   :    out Natural)
   is
   begin
      ASF.Find_Token (Source.Data.all, Set, Test, First, Last);
   end Find_Token;

   --  From
   procedure Find_Token
     (Source : in     Unbounded_String;
      From   : in     Positive;
      Set    : in     Ada.Strings.Maps.Character_Set;
      Test   : in     Ada.Strings.Membership;
      First  :    out Positive;
      Last   :    out Natural)
   is
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      ASF.Find_Token (Source.Data (From .. Source.Data'Last),
                      Set, Test, First, Last);
   end Find_Token;

   --  Slice
   procedure Find_Token
     (Source : in     Unbounded_String;
      Low    : in     Positive;
      High   : in     Natural;
      Set    : in     Ada.Strings.Maps.Character_Set;
      Test   : in     Ada.Strings.Membership;
      First  :    out Positive;
      Last   :    out Natural)
   is
   begin
      Slice_Check (Low, High, Source.Data'Last);
      ASF.Find_Token (Source.Data (Low .. High), Set, Test, First, Last);
   end Find_Token;

   ----------------------------------------------------------------------------
   --  First_Index.

   --  New
   function First_Index
     (Source : in Unbounded_String;
      Ch     : in Character)
     return Natural
   is
   begin
      return First_Index (Source.Data.all, Ch);
   end First_Index;

   --  New
   function First_Index
     (Source  : in Unbounded_String;
      Pattern : in String)
     return Natural
   is
   begin
      return First_Index (Source.Data.all, Pattern);
   end First_Index;

   --  New
   function First_Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String)
     return Natural
   is
   begin
      return First_Index (Source.Data.all, Pattern.Data.all);
   end First_Index;

   --  New, From
   function First_Index
     (Source : in Unbounded_String;
      Ch     : in Character;
      From   : in Positive)
     return Natural
   is
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      return First_Index (Source.Data (From .. Source.Data'Last), Ch);
   end First_Index;

   --  New, From
   function First_Index
     (Source  : in Unbounded_String;
      Pattern : in String;
      From    : in Positive)
     return Natural
   is
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      return First_Index (Source.Data (From .. Source.Data'Last), Pattern);
   end First_Index;

   --  New, From
   function First_Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      From    : in Positive)
     return Natural
   is
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      return First_Index (Source.Data (From .. Source.Data'Last),
                          Pattern.Data.all);
   end First_Index;

   ----------------------------------------------------------------------------
   --  From_Standard.

   --  New
   function From_Standard
     (S : in Ada.Strings.Unbounded.Unbounded_String)
     return Unbounded_String
   is
   begin
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with
             Data => new String'(ASU.To_String (S)));
   end From_Standard;

   --  New
   procedure From_Standard
     (Source : in     Ada.Strings.Unbounded.Unbounded_String;
      Target :    out Unbounded_String)
   is
   begin
      Free (Target.Data);
      Target.Data := new String'(ASU.To_String (Source));
   end From_Standard;

   ----------------------------------------------------------------------------
   --  From_String.

   --  New
   function From_String
     (S : in String)
     return Unbounded_String
   is
      Result : Unbounded_String;
   begin
      Result.Data := new String (1 .. S'Length);
      Result.Data.all := S;
      return Result;
   end From_String;

   ----------------------------------------------------------------------------
   --  Head.

   --  Standard
   function Head
     (Source : in Unbounded_String;
      Count  : in Natural;
      Pad    : in Character := Ada.Strings.Space)
     return Unbounded_String
   is
   begin
      if Count <= Source.Data'Last then
         return Slice (Source, 1, Count);
      else
         declare
            New_String : Unbounded_String :=
              (Ada.Finalization.Controlled with
                 Data => new String (1 .. Count));
         begin
            New_String.Data (1 .. Source.Data'Last) := Source.Data.all;
            for I in Source.Data'Last + 1 .. New_String.Data'Last loop
               New_String.Data (I) := Pad;
            end loop;
            return New_String;
         end;
      end if;
   end Head;

   --  Standard
   procedure Head
     (Source : in out Unbounded_String;
      Count  : in     Natural;
      Pad    : in     Character := Ada.Strings.Space)
   is
   begin
      if Count <= Source.Data'Last then
         declare
            New_String : constant String_Access :=
              Slice (Source.Data, 1, Count);
         begin
            Free (Source.Data);
            Source.Data := New_String;
         end;
      else
         declare
            New_String : constant String_Access := new String (1 .. Count);
         begin
            New_String (1 .. Source.Data'Last) := Source.Data.all;
            for I in Source.Data'Last + 1 .. New_String'Last loop
               New_String (I) := Pad;
            end loop;
            Free (Source.Data);
            Source.Data := New_String;
         end;
      end if;
   end Head;

   ----------------------------------------------------------------------------
   --  Index.

   --  Standard
   function Index
     (Source  : in Unbounded_String;
      Pattern : in String;
      Going   : in Ada.Strings.Direction              := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping :=
                     Ada.Strings.Maps.Identity)
     return Natural
   is
      use type Ada.Strings.Direction;
      pragma Suppress (Range_Check);
      --  No range check needed on the 'From' parameter.
   begin
      if Source.Data'Last = 0 then
         if Pattern'Last < Pattern'First then
            raise Pattern_Error;
         else
            return 0;
         end if;
      end if;
      if Going = Ada.Strings.Forward then
         return Index (Source, Pattern, 1, Going, Mapping);
      else
         return Index (Source, Pattern, Source.Data'Last, Going, Mapping);
      end if;
   end Index;

   --  Standard
   function Index
     (Source  : in Unbounded_String;
      Pattern : in String;
      Going   : in Ada.Strings.Direction := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Natural
   is
      use type Ada.Strings.Direction;
      pragma Suppress (Range_Check);
      --  No range check needed on the 'From' parameter.
   begin
      if Source.Data'Last = 0 then
         if Pattern'Last < Pattern'First then
            raise Pattern_Error;
         else
            return 0;
         end if;
      end if;
      if Going = Ada.Strings.Forward then
         return Index (Source, Pattern, 1, Going, Mapping);
      else
         return Index (Source, Pattern, Source.Data'Last, Going, Mapping);
      end if;
   end Index;

   --  Standard
   function Index
     (Source : in Unbounded_String;
      Set    : in Ada.Strings.Maps.Character_Set;
      Test   : in Ada.Strings.Membership := Ada.Strings.Inside;
      Going  : in Ada.Strings.Direction  := Ada.Strings.Forward)
     return Natural
   is
   begin
      return ASF.Index (Source.Data.all, Set, Test, Going);
   end Index;

   --  From
   function Index
     (Source  : in Unbounded_String;
      Pattern : in String;
      From    : in Positive;
      Going   : in Ada.Strings.Direction              := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping :=
                     Ada.Strings.Maps.Identity)
     return Natural
   is
      use type ASM.Character_Mapping;
      use type Ada.Strings.Direction;
   begin
      if Mapping = ASM.Identity then
         return Find (Source, Pattern, From, Going);
      elsif Pattern'Last < Pattern'First then
         raise Pattern_Error;
      elsif From > Source.Data'Last then
         raise Index_Error;
      end if;

      if (Going = Forward and then
          Pattern'Last - Pattern'First + 1 > Source.Data'Last - From + 1)
         or else
         (Going = Backward and then
          Pattern'Last - Pattern'First + 1 > From)
      then
         --  Pattern is longer than the source: cannot possibly match.
         return 0;
      end if;
      --  Only map the portion to be searched. Map on the heap: unbounded
      --  strings may be long!
      declare
         Temporary : Unbounded_String;
      begin
         if Going = Forward then
            Temporary.Data := new String (From .. Source.Data'Last);
            --  Yes, this time indexing *doesn't* start at 1... but this is
            --  used only internally, so this doesn't hurt.
            for I in Temporary.Data'Range loop
               Temporary.Data (I) := ASM.Value (Mapping, Source.Data (I));
            end loop;
            return First_Index (Temporary.Data.all, Pattern);
         else
            Temporary.Data := new String (1 .. From);
            for I in Temporary.Data'Range loop
               Temporary.Data (I) := ASM.Value (Mapping, Source.Data (I));
            end loop;
            return Last_Index (Temporary.Data.all, Pattern);
         end if;
      end;
   end Index;

   --  From
   function Index
     (Source  : in Unbounded_String;
      Pattern : in String;
      From    : in Positive;
      Going   : in Ada.Strings.Direction := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Natural
   is
      use type ASM.Character_Mapping_Function;
      use type Ada.Strings.Direction;
   begin
      if From > Source.Data'Last then
         raise Index_Error;
      elsif Pattern'Last < Pattern'First then
         raise Pattern_Error;
      elsif Mapping = null then
         raise Constraint_Error;
      end if;
      if (Going = Forward and then
          Pattern'Last - Pattern'First + 1 > Source.Data'Last - From + 1)
         or else
         (Going = Backward and then
          Pattern'Last - Pattern'First + 1 > From)
      then
         --  Pattern is longer than the source: cannot possibly match.
         return 0;
      end if;
      --  Only map the portion to be searched. Map on the heap: unbounded
      --  strings may be long!
      declare
         Temporary : Unbounded_String;
      begin
         if Going = Forward then
            Temporary.Data := new String (From .. Source.Data'Last);
            --  Yes, this time indexing *doesn't* start at 1... but this is
            --  used only internally, so this doesn't hurt.
            for I in Temporary.Data'Range loop
               Temporary.Data (I) := Mapping (Source.Data (I));
            end loop;
            return First_Index (Temporary.Data.all, Pattern);
         else
            Temporary.Data := new String (1 .. From);
            for I in Temporary.Data'Range loop
               Temporary.Data (I) := Mapping (Source.Data (I));
            end loop;
            return Last_Index (Temporary.Data.all, Pattern);
         end if;
      end;
   end Index;

   --  From
   function Index
     (Source : in Unbounded_String;
      From   : in Positive;
      Set    : in Ada.Strings.Maps.Character_Set;
      Test   : in Ada.Strings.Membership := Ada.Strings.Inside;
      Going  : in Ada.Strings.Direction  := Ada.Strings.Forward)
     return Natural
   is
   begin
      if From > Source.Data'Last then raise Index_Error; end if;
      return
        ASF.Index (Source.Data (From .. Source.Data'Last), Set, Test, Going);
   end Index;

   --  Unbounded
   function Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      Going   : in Ada.Strings.Direction              := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping :=
                     Ada.Strings.Maps.Identity)
     return Natural
   is
   begin
      return Index (Source, Pattern.Data.all, Going, Mapping);
   end Index;

   --  Unbounded
   function Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      Going   : in Ada.Strings.Direction              := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Natural
   is
   begin
      return Index (Source, Pattern.Data.all, Going, Mapping);
   end Index;

   --  Unbounded, From
   function Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      From    : in Positive;
      Going   : in Ada.Strings.Direction              := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping :=
                     Ada.Strings.Maps.Identity)
     return Natural
   is
   begin
      return Index (Source, Pattern.Data.all, From, Going, Mapping);
   end Index;

   --  Unbounded, From
   function Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      From    : in Positive;
      Going   : in Ada.Strings.Direction              := Ada.Strings.Forward;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Natural
   is
   begin
      return Index (Source, Pattern.Data.all, From, Going, Mapping);
   end Index;

   ----------------------------------------------------------------------------
   --  Index_Non_Blank.

   --  Standard
   function Index_Non_Blank
     (Source : in Unbounded_String;
      Going  : in Ada.Strings.Direction := Ada.Strings.Forward)
     return Natural
   is
   begin
      return ASF.Index_Non_Blank (Source.Data.all, Going);
   end Index_Non_Blank;

   --  From
   function Index_Non_Blank
     (Source : in Unbounded_String;
      From   : in Positive;
      Going  : in Ada.Strings.Direction := Ada.Strings.Forward)
     return Natural
   is
   begin
      if From > Source.Data'Last + 1 then raise Index_Error; end if;
      return
        ASF.Index_Non_Blank (Source.Data (From .. Source.Data'Last), Going);
   end Index_Non_Blank;

   ----------------------------------------------------------------------------
   --  Insert.

   --  Standard
   function Insert
     (Source   : in Unbounded_String;
      Before   : in Positive;
      New_Item : in String)
     return Unbounded_String
   is
   begin
      if Before > Source.Data'Last + 1 then raise Index_Error; end if;
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with
             Data => Insert (Source.Data, Before, New_Item));
   end Insert;

   --  Standard
   procedure Insert
     (Source   : in out Unbounded_String;
      Before   : in     Positive;
      New_Item : in     String)
   is
   begin
      if Before > Source.Data'Last + 1 then raise Index_Error; end if;
      declare
         New_String : constant String_Access :=
           Insert (Source.Data, Before, New_Item);
      begin
         Free (Source.Data);
         Source.Data := New_String;
      end;
   end Insert;

   --  Unbounded
   function Insert
     (Source   : in Unbounded_String;
      Before   : in Positive;
      New_Item : in Unbounded_String)
     return Unbounded_String
   is
   begin
      return Insert (Source, Before, New_Item.Data.all);
   end Insert;

   --  Unbounded
   procedure Insert
     (Source   : in out Unbounded_String;
      Before   : in     Positive;
      New_Item : in     Unbounded_String)
   is
   begin
      Insert (Source, Before, New_Item.Data.all);
   end Insert;

   --  Slice
   function Insert
     (Source   : in Unbounded_String;
      Before   : in Positive;
      New_Item : in Unbounded_String;
      Low      : in Positive;
      High     : in Natural)
     return Unbounded_String
   is
   begin
      Slice_Check (Low, High, New_Item.Data'Last);
      return Insert (Source, Before, New_Item.Data (Low .. High));
   end Insert;

   --  Slice
   procedure Insert
     (Source   : in out Unbounded_String;
      Before   : in     Positive;
      New_Item : in     Unbounded_String;
      Low      : in     Positive;
      High     : in     Natural)
   is
   begin
      Slice_Check (Low, High, New_Item.Data'Last);
      Insert (Source, Before, New_Item.Data (Low .. High));
   end Insert;

   ----------------------------------------------------------------------------
   --  Is_Prefix.

   --  New
   function Is_Prefix
     (Source : in Unbounded_String;
      Prefix : in String)
     return Boolean
   is
   begin
      return Is_Prefix (Source.Data.all, Prefix);
   end Is_Prefix;

   --  New
   function Is_Prefix
     (Source : in Unbounded_String;
      Prefix : in Unbounded_String)
     return Boolean
   is
   begin
      return Is_Prefix (Source.Data.all, Prefix.Data.all);
   end Is_Prefix;

   ----------------------------------------------------------------------------
   --  Is_Suffix.

   --  New
   function Is_Suffix
     (Source : in Unbounded_String;
      Suffix : in String)
     return Boolean
   is
   begin
      return Is_Suffix (Source.Data.all, Suffix);
   end Is_Suffix;

   --  New
   function Is_Suffix
     (Source : in Unbounded_String;
      Suffix : in Unbounded_String)
     return Boolean
   is
   begin
      return Is_Suffix (Source.Data.all, Suffix.Data.all);
   end Is_Suffix;

   ----------------------------------------------------------------------------
   --  Last_Index.

   --  New
   function Last_Index
     (Source : in Unbounded_String;
      Ch     : in Character)
     return Natural
   is
   begin
      return Last_Index (Source.Data.all, Ch);
   end Last_Index;

   --  New
   function Last_Index
     (Source  : in Unbounded_String;
      Pattern : in String)
     return Natural
   is
   begin
      return Last_Index (Source.Data.all, Pattern);
   end Last_Index;

   --  New
   function Last_Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String)
     return Natural
   is
   begin
      return Last_Index (Source.Data.all, Pattern.Data.all);
   end Last_Index;

   --  New, From
   function Last_Index
     (Source : in Unbounded_String;
      Ch     : in Character;
      Limit  : in Positive)
     return Natural
   is
   begin
      if Limit > Source.Data'Last then raise Index_Error; end if;
      return Last_Index (Source.Data (1 .. Limit), Ch);
   end Last_Index;

   --  New, From
   function Last_Index
     (Source  : in Unbounded_String;
      Pattern : in String;
      Limit   : in Positive)
     return Natural
   is
   begin
      if Limit > Source.Data'Last then raise Index_Error; end if;
      return Last_Index (Source.Data (1 .. Limit), Pattern);
   end Last_Index;

   --  New, From
   function Last_Index
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String;
      Limit   : in Positive)
     return Natural
   is
   begin
      if Limit > Source.Data'Last then raise Index_Error; end if;
      return Last_Index (Source.Data (1 .. Limit), Pattern.Data.all);
   end Last_Index;

   ----------------------------------------------------------------------------
   --  Length.

   --  Standard
   function Length
     (Source : in Unbounded_String)
     return Natural
   is
   begin
      return Source.Data'Length;
   end Length;

   ----------------------------------------------------------------------------
   --  Overwrite.

   --  New
   function Occurrences
     (Source  : in Unbounded_String;
      Pattern : in String)
     return Natural
   is
   begin
      return Count (Source.Data.all, Pattern);
   end Occurrences;

   --  New
   function  Occurrences
     (Source  : in Unbounded_String;
      Pattern : in Unbounded_String)
     return Natural
   is
   begin
      return Count (Source.Data.all, Pattern.Data.all);
   end Occurrences;

   ----------------------------------------------------------------------------
   --  Overwrite.

   --  Standard
   function Overwrite
     (Source   : in Unbounded_String;
      Position : in Positive;
      New_Item : in String)
     return Unbounded_String
   is
   begin
      if Position > Source.Data'Last + 1 then raise Index_Error; end if;
      declare
         Length     : constant Natural := New_Item'Length;
         New_String : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (1 .. Integer'Max (Source.Data'Last,
                                                    Position - 1 + Length)));
      begin
         New_String.Data (1 .. Position - 1) :=
           Source.Data (1 .. Position - 1);
         New_String.Data (Position .. Position + Length - 1) :=  New_Item;
         New_String.Data (Position + Length .. New_String.Data'Last) :=
           Source.Data (Position + Length .. Source.Data'Last);
         return New_String;
      end;
   end Overwrite;

   --  Standard
   procedure Overwrite
     (Source   : in out Unbounded_String;
      Position : in     Positive;
      New_Item : in     String)
   is
      Length : constant Natural := New_Item'Length;
   begin
      if Position <= Source.Data'Last - Length + 1 then
         Source.Data (Position .. Position + Length - 1) := New_Item;
      elsif Position > Source.Data'Last + 1 then
         raise Index_Error;
      else
         declare
            New_String : constant String_Access :=
              new String (1 .. Position - 1 + Length);
         begin
            New_String (1 .. Position - 1)           :=
              Source.Data (1 .. Position - 1);
            New_String (Position .. New_String'Last) := New_Item;
            Free (Source.Data);
            Source.Data := New_String;
         end;
      end if;
   end Overwrite;

   --  Unbounded
   function Overwrite
     (Source   : in Unbounded_String;
      Position : in Positive;
      New_Item : in Unbounded_String)
     return Unbounded_String
   is
   begin
      return Overwrite (Source, Position, New_Item.Data.all);
   end Overwrite;

   --  Unbounded
   procedure Overwrite
     (Source   : in out Unbounded_String;
      Position : in     Positive;
      New_Item : in     Unbounded_String)
   is
   begin
      Overwrite (Source, Position, New_Item.Data.all);
   end Overwrite;

   --  Slice
   function Overwrite
     (Source   : in Unbounded_String;
      Position : in Positive;
      New_Item : in Unbounded_String;
      Low      : in Positive;
      High     : in Natural)
     return Unbounded_String
   is
   begin
      Slice_Check (Low, High, New_Item.Data'Last);
      return Overwrite (Source, Position, New_Item.Data (Low .. High));
   end Overwrite;

   --  Slice
   procedure Overwrite
     (Source   : in out Unbounded_String;
      Position : in     Positive;
      New_Item : in     Unbounded_String;
      Low      : in     Positive;
      High     : in     Natural)
   is
   begin
      Slice_Check (Low, High, New_Item.Data'Last);
      Overwrite (Source, Position, New_Item.Data (Low .. High));
   end Overwrite;

   ----------------------------------------------------------------------------
   --  Replace.

   --  New
   procedure Replace
     (Source : in out Unbounded_String;
      What   : in     String;
      By     : in     String)
   is
      N : constant Natural := Count (Source.Data.all, What);
   begin
      if N = 0 then return; end if;
      declare
         By_Length   : constant Natural := By'Length;
         What_Length : constant Natural := What'Length;
         Result      : constant String_Access :=
           new String (1 ..
                       Source.Data'Last - N * (What_Length - By_Length));
         I, J, K     : Natural;
      begin
         J := 1;
         I := Source.Data'First;
         while I <= Source.Data'Last loop
            K := First_Index (Source.Data (I .. Source.Data'Last), What);
            if K = 0 then
               Result (J .. Result'Last) :=
                 Source.Data (I .. Source.Data'Last);
               I := Source.Data'Last + 1;
            else
               Result (J .. J + (K - I) - 1) := Source.Data (I .. K - 1);
               J := J + (K - I);
               Result (J .. J + By_Length - 1) := By;
               J := J + By_Length;
               I := K + What_Length;
            end if;
         end loop;
         Free (Source.Data);
         Source.Data := Result;
      end;
   end Replace;

   --  New, Unbounded
   procedure Replace
     (Source : in out Unbounded_String;
      What   : in     Unbounded_String;
      By     : in     Unbounded_String)
   is
   begin
      Replace (Source, What.Data.all, By.Data.all);
   end Replace;

   ----------------------------------------------------------------------------
   --  Replace_Element.

   --  Standard
   procedure Replace_Element
     (Source : in out Unbounded_String;
      Index  : in     Positive;
      By     : in     Character)
   is
   begin
      Source.Data (Index) := By;
   end Replace_Element;

   ----------------------------------------------------------------------------
   --  Replace_Slice.

   --  Standard
   function Replace_Slice
     (Source : in Unbounded_String;
      Low    : in Positive;
      High   : in Natural;
      By     : in String)
     return Unbounded_String
   is
   begin
      Slice_Check (Low, High, Source.Data'Last);
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with
             Data => Replace_Slice (Source.Data, Low, High, By));
   end Replace_Slice;

   --  Standard
   procedure Replace_Slice
     (Source : in out Unbounded_String;
      Low    : in     Positive;
      High   : in     Natural;
      By     : in     String)
   is
   begin
      Slice_Check (Low, High, Source.Data'Last);
      declare
         New_String : constant String_Access :=
           Replace_Slice (Source.Data, Low, High, By);
      begin
         Free (Source.Data);
         Source.Data := New_String;
      end;
   end Replace_Slice;

   --  Unbounded
   function  Replace_Slice
     (Source : in Unbounded_String;
      Low    : in Positive;
      High   : in Natural;
      By     : in Unbounded_String)
     return Unbounded_String
   is
   begin
      return Replace_Slice (Source, Low, High, By.Data.all);
   end Replace_Slice;

   --  Unbounded
   procedure Replace_Slice
     (Source : in out Unbounded_String;
      Low    : in     Positive;
      High   : in     Natural;
      By     : in     Unbounded_String)
   is
   begin
      Replace_Slice (Source, Low, High, By.Data.all);
   end Replace_Slice;

   --  Slice
   function  Replace_Slice
     (Source : in Unbounded_String;
      Low    : in Positive;
      High   : in Natural;
      By     : in Unbounded_String;
      From   : in Positive;
      To     : in Natural)
     return Unbounded_String
   is
   begin
      Slice_Check (From, To, By.Data'Last);
      return Replace_Slice (Source, Low, High, By.Data (From .. To));
   end Replace_Slice;

   --  Slice
   procedure Replace_Slice
     (Source : in out Unbounded_String;
      Low    : in     Positive;
      High   : in     Natural;
      By     : in     Unbounded_String;
      From   : in     Positive;
      To     : in     Natural)
   is
   begin
      Slice_Check (From, To, By.Data'Last);
      Replace_Slice (Source, Low, High, By.Data (From .. To));
   end Replace_Slice;

   ----------------------------------------------------------------------------
   --  Set.

   --  New
   procedure Set
     (Target : in out Unbounded_String;
      Str    : in     String)
   is
   begin
      Free (Target.Data);
      if Str'Last >= Str'First then
         Target.Data     := new String (1 .. Str'Length);
         Target.Data.all := Str;
      else
         Target.Data := Null_String'Access;
      end if;
   end Set;

   ----------------------------------------------------------------------------
   --  Slice.

   --  Standard
   function Slice
     (Source : in Unbounded_String;
      Low    : in Positive;
      High   : in Natural)
     return String
   is
   begin
      Slice_Check (Low, High, Source.Data'Last);
      return Source.Data (Low .. High);
   end Slice;

   --  Unbounded
   function Slice
     (Source : in Unbounded_String;
      Low    : in Positive;
      High   : in Natural)
     return Unbounded_String
   is
   begin
      Slice_Check (Low, High, Source.Data'Last);
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with
             Data => Slice (Source.Data, Low, High));
   end Slice;

   --  Slice
   procedure Slice
     (Target : in out Unbounded_String;
      Source : in     Unbounded_String;
      Low    : in     Positive;
      High   : in     Natural)
   is
   begin
      Slice_Check (Low, High, Source.Data'Last);
      declare
         New_String : constant String_Access := Slice (Source.Data, Low, High);
      begin
         Free (Target.Data);
         Target.Data := New_String;
      end;
   end Slice;

   ----------------------------------------------------------------------------
   --  Tail.

   --  Standard
   function Tail
     (Source : in Unbounded_String;
      Count  : in Natural;
      Pad    : in Character := Ada.Strings.Space)
     return Unbounded_String
   is
   begin
      if Count <= Source.Data'Last then
         return Slice (Source, Source.Data'Last - Count + 1, Source.Data'Last);
      else
         declare
            New_String : Unbounded_String :=
              (Ada.Finalization.Controlled with
                 Data => new String (1 .. Count));
         begin
            New_String.Data (New_String.Data'Last - Source.Data'Last + 1 ..
                             New_String.Data'Last) := Source.Data.all;
            for I in 1 .. New_String.Data'Last - Source.Data'Last loop
               New_String.Data (I) := Pad;
            end loop;
            return New_String;
         end;
      end if;
   end Tail;

   --  Standard.
   procedure Tail
     (Source : in out Unbounded_String;
      Count  : in     Natural;
      Pad    : in     Character := Ada.Strings.Space)
   is
   begin
      if Count <= Source.Data'Last then
         declare
            New_String : constant String_Access :=
              Slice (Source.Data,
                     Source.Data'Last - Count + 1, Source.Data'Last);
         begin
            Free (Source.Data);
            Source.Data := New_String;
         end;
      else
         declare
            New_String : constant String_Access := new String (1 .. Count);
         begin
            New_String (New_String'Last - Source.Data'Last + 1 ..
                        New_String'Last) := Source.Data.all;
            for I in 1 .. New_String'Last - Source.Data'Last loop
               New_String (I) := Pad;
            end loop;
            Free (Source.Data);
            Source.Data := New_String;
         end;
      end if;
   end Tail;

   ----------------------------------------------------------------------------
   --  To_Standard.

   --  New
   function To_Standard
     (S : in Unbounded_String)
     return Ada.Strings.Unbounded.Unbounded_String
   is
   begin
      return ASU.To_Unbounded_String (S.Data.all);
   end To_Standard;

   --  New
   procedure To_Standard
     (Source : in     Unbounded_String;
      Target :    out Ada.Strings.Unbounded.Unbounded_String)
   is
   begin
      Target := ASU.To_Unbounded_String (Source.Data.all);
   end To_Standard;

   ----------------------------------------------------------------------------
   --  To_String

   --  Standard
   function To_String
     (S : in Unbounded_String)
     return String
   is
   begin
      return S.Data.all;
   end To_String;

   ----------------------------------------------------------------------------
   --  To_Unbounded_String.

   --  Standard
   function To_Unbounded_String
     (Length : in Natural)
     return Unbounded_String
   is
   begin
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with Data => new String (1 .. Length));
   end To_Unbounded_String;

   ----------------------------------------------------------------------------
   --  Translate.

   --  Standard
   function Translate
     (Source  : in Unbounded_String;
      Mapping : in Ada.Strings.Maps.Character_Mapping)
     return Unbounded_String
   is
      Result : Unbounded_String :=
        (Ada.Finalization.Controlled with
           Data => new String (Source.Data'Range));
   begin
      for I in Result.Data'Range loop
         Result.Data (I) := ASM.Value (Mapping, Source.Data (I));
      end loop;
      return Result;
   end Translate;

   --  Standard
   procedure Translate
     (Source  : in out Unbounded_String;
      Mapping : in     Ada.Strings.Maps.Character_Mapping)
   is
   begin
      ASF.Translate (Source.Data.all, Mapping);
   end Translate;

   --  Slice
   function Translate
     (Source  : in Unbounded_String;
      From    : in Positive;
      Through : in Natural;
      Mapping : in Ada.Strings.Maps.Character_Mapping)
     return Unbounded_String
   is
   begin
      Slice_Check (From, Through, Source.Data'Last);
      if From > Through then return Null_Unbounded_String; end if;
      declare
         Result : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (1 .. Through - From + 1));
      begin
         for I in Result.Data'Range loop
            Result.Data (I) := ASM.Value (Mapping, Source.Data (From + I));
         end loop;
         return Result;
      end;
   end Translate;

   --  Slice
   procedure Translate
     (Source  : in out Unbounded_String;
      From    : in     Positive;
      Through : in     Natural;
      Mapping : in     Ada.Strings.Maps.Character_Mapping)
   is
   begin
      Slice_Check (From, Through, Source.Data'Last);
      ASF.Translate (Source.Data (From .. Through), Mapping);
   end Translate;

   --  Standard
   function Translate
     (Source  : in Unbounded_String;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Unbounded_String
   is
      use type ASM.Character_Mapping_Function;
   begin
      if Mapping = null then raise Constraint_Error; end if;
      declare
         Result : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (Source.Data'Range));
      begin
         for I in Result.Data'Range loop
            Result.Data (I) := Mapping (Source.Data (I));
         end loop;
         return Result;
      end;
   end Translate;

   --  Standard
   procedure Translate
     (Source  : in out Unbounded_String;
      Mapping : in     Ada.Strings.Maps.Character_Mapping_Function)
   is
   begin
      ASF.Translate (Source.Data.all, Mapping);
   end Translate;

   --  Slice
   function Translate
     (Source  : in Unbounded_String;
      From    : in Positive;
      Through : in Natural;
      Mapping : in Ada.Strings.Maps.Character_Mapping_Function)
     return Unbounded_String
   is
      use type ASM.Character_Mapping_Function;
   begin
      Slice_Check (From, Through, Source.Data'Last);
      if From > Through then
         return Null_Unbounded_String;
      elsif Mapping = null then
         raise Constraint_Error;
      end if;
      declare
         Result : Unbounded_String :=
           (Ada.Finalization.Controlled with
              Data => new String (1 .. Through - From + 1));
      begin
         for I in Result.Data'Range loop
            Result.Data (I) := Mapping (Source.Data (From + I));
         end loop;
         return Result;
      end;
   end Translate;

   --  Slice
   procedure Translate
     (Source  : in out Unbounded_String;
      From    : in     Positive;
      Through : in     Natural;
      Mapping : in     Ada.Strings.Maps.Character_Mapping_Function)
   is
   begin
      Slice_Check (From, Through, Source.Data'Last);
      ASF.Translate (Source.Data (From .. Through), Mapping);
   end Translate;

   ----------------------------------------------------------------------------
   --  Trim.

   --  Standard
   function Trim
     (Source : in Unbounded_String;
      Side   : in Ada.Strings.Trim_End)
     return Unbounded_String
   is
      I, J : Natural;
   begin
      Trim (Source.Data.all, Side, I, J);
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with
             Data => Slice (Source.Data, I, J));
   end Trim;

   --  Standard
   procedure Trim
     (Source : in out Unbounded_String;
      Side   : in     Ada.Strings.Trim_End)
   is
      I, J : Natural;
   begin
      Trim (Source.Data.all, Side, I, J);
      --  What remains is Source.Data (I .. J).
      declare
         New_String : constant String_Access := Slice (Source.Data, I, J);
      begin
         Free (Source.Data);
         Source.Data := New_String;
      end;
   end Trim;

   --  Standard
   function Trim
     (Source : in Unbounded_String;
      Left   : in Ada.Strings.Maps.Character_Set;
      Right  : in Ada.Strings.Maps.Character_Set)
     return Unbounded_String
   is
      I, J : Natural;
   begin
      Trim (Source.Data.all, Left, Right, I, J);
      return
        Unbounded_String'
          (Ada.Finalization.Controlled with
             Data => Slice (Source.Data, I, J));
   end Trim;

   --  Standard
   procedure Trim
     (Source : in out Unbounded_String;
      Left   : in     Ada.Strings.Maps.Character_Set;
      Right  : in     Ada.Strings.Maps.Character_Set)
   is
      I, J : Natural;
   begin
      Trim (Source.Data.all, Left, Right, I, J);
      declare
         New_String : constant String_Access := Slice (Source.Data, I, J);
      begin
         Free (Source.Data);
         Source.Data := New_String;
      end;
   end Trim;

   ----------------------------------------------------------------------------
   --  Controlled operations

   procedure Initialize (S : in out Unbounded_String)
   is
   begin
      S.Data := Null_String'Access;
   end Initialize;

   procedure Adjust     (S : in out Unbounded_String)
   is
   begin
      if S.Data /= Null_String'Access then
         S.Data := new String'(S.Data.all);
      end if;
   end Adjust;

   procedure Finalize   (S : in out Unbounded_String)
   is
   begin
      Free (S.Data);
   end Finalize;

   ----------------------------------------------------------------------------
   --  Stream operations

   procedure Write  (Stream : access Ada.Streams.Root_Stream_Type'Class;
                     S      : in     Unbounded_String)
   is
   begin
      Natural'Write (Stream, S.Data'Last);
      if S.Data'Last > 0 then
         String'Write (Stream, S.Data.all);
      end if;
   end Write;

   procedure Read   (Stream : access Ada.Streams.Root_Stream_Type'Class;
                     S      :    out Unbounded_String)
   is
      N : Natural;
   begin
      Free (S.Data);
      Natural'Read (Stream, N);
      if N = 0 then
         S.Data := Null_String'Access;
      else
         S.Data := new String (1 .. N);
         String'Read (Stream, S.Data.all);
      end if;
   end Read;

end Util.Text;
