{ +-------------------------------------------------------------+ }
{ |                                                             | }
{ |   GM-Software                                               | }
{ |   ===========                                               | }
{ |                                                             | }
{ |   Project: All Projects                                     | }
{ |                                                             | }
{ |   Description: String builder that speeds up assembling     | }
{ |                large string from smaller pieces.            | }
{ |                                                             | }
{ |   Copyright (C) - 2024 - Gerrit Moeller.                    | }
{ |                                                             | }
{ |   Source code distributed under MIT license.                | }
{ |                                                             | }
{ |   See: https://www.gm-software.de                           | }
{ |                                                             | }
{ +-------------------------------------------------------------+ }

{$INCLUDE GMCompilerSettings.inc}

unit GMStrBuilder;

interface

uses GMStrDef;

type

  {$if FPC_FULLVERSION < 30101}
    {$ERROR The string builder code needs FPC version >= 3.1.1. If you are using Delphi make sure that custom managed record operators are supported}
  {$endif}

  //
  // TStrType is meant to be a string data type like: UnicodeString, WideString, AnsiString, RawByteString, Utf8String or AnsiString(CP_XXXX)
  //
  RGMGenericStringBuilder<TStrType> = record
   private
    class operator Implicit(AValue: UnicodeString): RGMGenericStringBuilder<TStrType>;
    class operator Implicit(AValue: WideString): RGMGenericStringBuilder<TStrType>;
    class operator Implicit(AValue: AnsiString): RGMGenericStringBuilder<TStrType>;
    class operator Implicit(AValue: Utf8String): RGMGenericStringBuilder<TStrType>;
    class operator Implicit(AValue: RawByteString): RGMGenericStringBuilder<TStrType>;

    class operator Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): UnicodeString;
    class operator Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): WideString;
    class operator Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): AnsiString;
    class operator Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): Utf8String;
    class operator Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): RawByteString;

    procedure Grow(AAddLen: Integer);

   public
    StrBufAllocAlign: Integer;
    Len: Integer;
    StrBuffer: TStrType;

    class operator Initialize(var AInstance: RGMGenericStringBuilder<TStrType>);
    class operator Add(AValue1: RGMGenericStringBuilder<TStrType>; AValue2: UnicodeString): RGMGenericStringBuilder<TStrType>;
    function Append(AStr: TStrType): Integer; overload;
    function Append(AChar: AnsiChar): Integer; overload;
    function Append(AChar: WideChar): Integer; overload;

    function Join(ASeparator, AStr: TStrType): Integer;

    procedure Clear(AFreeMemory: Boolean = False);

    //class operator Finalize(var AValue: RProgramMemory);
  end;


  RGMStringBuilder = RGMGenericStringBuilder<TGMString>;


var vDfltStrBufAllocAlign: Integer = $2000; // <- 8 KiB


implementation

{ ------------------------------------------- }
{ ---- RGMGenericStringBuilder<TStrType> ---- }
{ ------------------------------------------- }

class operator RGMGenericStringBuilder<TStrType>.Initialize(var AInstance: RGMGenericStringBuilder<TStrType>);
begin
  AInstance.Len := 0;
  AInstance.StrBuffer := '';
  AInstance.StrBufAllocAlign := vDfltStrBufAllocAlign;
end;

class operator RGMGenericStringBuilder<TStrType>.Add(AValue1: RGMGenericStringBuilder<TStrType>; AValue2: UnicodeString): RGMGenericStringBuilder<TStrType>;
begin
  AValue1.Append(AValue2);
  Result := AValue1;
end;

procedure RGMGenericStringBuilder<TStrType>.Grow(AAddLen: Integer);
begin
 if Len + AAddLen > Length(StrBuffer) then
  begin
   SetLength(StrBuffer, Align(Len + AAddLen, StrBufAllocAlign));
  end;
end;

function RGMGenericStringBuilder<TStrType>.Append(AStr: TStrType): Integer;
var addLen: Integer;
begin
  if Len <= 0 then
   begin
    StrBuffer := AStr;
    Len := Length(StrBuffer);
   end
  else
   begin
    addLen := Length(AStr);
    if addLen > 0 then
     begin
      Grow(addLen);
      System.Move(AStr[1], StrBuffer[Len+1], addLen * SizeOf(StrBuffer[1]));
      //for i:=1 to addLen do StrBuffer[Len + i] := AStr[i];
      Len += addLen;
     end;
   end;
  Result := Len;
end;

function RGMGenericStringBuilder<TStrType>.Append(AChar: AnsiChar): Integer;
begin
  Grow(1);
  StrBuffer[Len + 1] := AChar;
  Len += 1;
  Result := Len;
end;

function RGMGenericStringBuilder<TStrType>.Append(AChar: WideChar): Integer;
begin
  Grow(1);
  StrBuffer[Len + 1] := AChar;
  Len += 1;
  Result := Len;
end;

function RGMGenericStringBuilder<TStrType>.Join(ASeparator, AStr: TStrType): Integer;
begin
  if Length(AStr) > 0 then
   begin
    if Len <= 0 then Append(AStr) else Append(ASeparator + AStr);
   end;

  Result := Len;
end;

procedure RGMGenericStringBuilder<TStrType>.Clear(AFreeMemory: Boolean);
begin
  if AFreeMemory then StrBuffer := '';
  Len := 0;
end;


{$macro on}
{$define CommonAssignFromStr:=
  Result.StrBuffer := AValue;
  Result.Len := Length(Result.StrBuffer);}

class operator RGMGenericStringBuilder<TStrType>.Implicit(AValue: UnicodeString): RGMGenericStringBuilder<TStrType>;
begin
  CommonAssignFromStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(AValue: WideString): RGMGenericStringBuilder<TStrType>;
begin
  CommonAssignFromStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(AValue: AnsiString): RGMGenericStringBuilder<TStrType>;
begin
  CommonAssignFromStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(AValue: Utf8String): RGMGenericStringBuilder<TStrType>;
begin
  CommonAssignFromStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(AValue: RawByteString): RGMGenericStringBuilder<TStrType>;
begin
  CommonAssignFromStr
end;


{$define CommonAssignToStr:=
  if (AStrBuilder.Len = Length(AStrBuilder.StrBuffer)) then
    Result := AStrBuilder.StrBuffer
  else
    Result := System.Copy(AStrBuilder.StrBuffer, 1, AStrBuilder.Len);}

class operator RGMGenericStringBuilder<TStrType>.Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): UnicodeString;
begin
  CommonAssignToStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): WideString;
begin
  CommonAssignToStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): AnsiString;
begin
  CommonAssignToStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): Utf8String;
begin
  CommonAssignToStr
end;

class operator RGMGenericStringBuilder<TStrType>.Implicit(const AStrBuilder: RGMGenericStringBuilder<TStrType>): RawByteString;
begin
  CommonAssignToStr
end;


end.