ExString for fast handling of large strings

Share your advanced PureBasic knowledge/code with the community.
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

ExString for fast handling of large strings

Post by ChrisR »

This is a rework of HeX0R's StringBuilder module version from ->here<-
Before he used an interface for easier handling ->here<-.

To avoid any confusion, the module is renamed to ExString, with additional functions added:
  • Add, Insert, Concat, Reset, Len, Get, Left, Right, Mid, LSet, RSet, LCase, UCase
The goal was mainly educational for me, before using it :wink:

Code: Select all

;- Top
; -----------------------------------------------------------------------------
;  Name       : ExString module
;  Description: Concatenation and manipulation of large strings from the pointer in memory. With a huge gain in speed for large strings vs PB.
;               It avoids the bottleneck in PB to get the string length with the null character that indicates the end of the string.
;  Target-OS  : Cross-platform
;  
;  Based on   : StringBuilder
;  Author     : HeX0R
;  Date       : 2020-08-03
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?p=558277#p558277
;  Updated    : 2023-02-16 use an interface now for easier handling
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?t=80831
;  
;  Based on   : Based on FastString - Structured Memory String
;  Author     : mk-soft
;  Date       : 2020-07-25
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?t=75758
;
;  Renamed    : Renamed the module to ExString to avoid any confusion with the HeX0R's original module
;  Change     : Remove the Ex String List
;               Additional functions (with Prefix ES here): Add, Insert, Concat, Reset, Len, Get Left, Right, Mid, LSet, RSet, LCase, UCase
;  Author     : ChrisR
;  Date       : 2023-02-16
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?t=80832
;
;  Test       : To be used without the Debug option to measure and compare the execution time between ExString and native PB string functions
;               Or use it with the Debug option to test the different functions.
; -----------------------------------------------------------------------------

DeclareModule ExString
  
  Declare   NewES()
  Declare   ResetES(*THIS, KeepMemory = #False)
  Declare   AddES(*THIS, String.s)
  Declare   InsertES(*THIS, String.s, Position = 1)
  Declare   ConcatES(*THIS, *THISAdd)
  Declare   LenES(*THIS)
  Declare   UsedES(*THIS)
  Declare.s GetES(*THIS)
  Declare.s LeftES(*THIS, Length)
  Declare.s RightES(*THIS, Length)
  Declare.s MidES(*THIS, Position, Length = #PB_Ignore)
  Declare.s LSetES(*THIS, Length, Char.s = " ", Flag = #PB_Default)   ; Flag = #PB_Default | #PB_String_InPlace
  Declare.s RSetES(*THIS, Length, Char.s = " ", Flag = #PB_Default)   ; Flag = #PB_Default | #PB_String_InPlace
  Declare.s LCutES(*THIS, Length, Flag = #PB_Default)                 ; Length = Specifies how many characters to delete on the left, Flag = #PB_Default | #PB_String_InPlace
  Declare.s RCutES(*THIS, Length, Flag = #PB_Default)                 ; Length = Specifies how many characters to delete on the right, Flag = #PB_Default | #PB_String_InPlace
  Declare.s LCaseES(*THIS, Flag = #PB_Default)                        ; Flag = #PB_Default | #PB_String_InPlace
  Declare.s UCaseES(*THIS, Flag = #PB_Default)                        ; Flag = #PB_Default | #PB_String_InPlace
  Declare   FreeES(*THIS)
  
EndDeclareModule

Module ExString
  EnableExplicit
  
  #PacketSize = 8192   ; 65536
  #DebugCountUsed = #False   ; #True | #False
  
  Structure ST_ExString
    *Address
    *Pointer
    MemSize.i
    Length.i
    CompilerIf #DebugCountUsed
      Used.i
    CompilerEndIf
  EndStructure
  
  Declare CheckResizeMemory(*THIS, Length)
  
  Macro ProcedureReturnIf(Cond, ReturnVal = 0)
    If Cond
      ProcedureReturn ReturnVal
    EndIf
  EndMacro
  
  Macro ProcedureReturnSIf(Cond)
    If Cond
      ProcedureReturn
    EndIf
  EndMacro
  
  Procedure CheckResizeMemory(*THIS.ST_ExString, Length)
    Protected *Add, Result = #True
    
    While Length + *THIS\Length + SizeOf(Character) > *THIS\MemSize
      *THIS\MemSize + #PacketSize
      *Add = ReAllocateMemory(*THIS\Address, *THIS\MemSize, #PB_Memory_NoClear)
      If *Add
        If *Add <> *THIS\Address
          *THIS\Pointer = *THIS\Pointer - *THIS\Address + *Add
          *THIS\Address = *Add
        EndIf
      Else
        Result = #False
        Break
      EndIf
    Wend
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure NewES()
    Protected *Buffer, Result
    
    *Buffer = AllocateMemory(#PacketSize, #PB_Memory_NoClear)
    If *Buffer
      Protected *THIS.ST_ExString
      *THIS = AllocateStructure(ST_ExString)
      *THIS\Address     = *Buffer
      *THIS\Pointer     = *Buffer
      *THIS\MemSize     = #PacketSize
      Result           = *THIS
    EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure ResetES(*THIS.ST_ExString, KeepMemory = #False)
    ProcedureReturnIf(*THIS\Address = 0)

    If KeepMemory = #False And *THIS\MemSize > #PacketSize
      *THIS\Address = ReAllocateMemory(*THIS\Address, #PacketSize, #PB_Memory_NoClear)
      *THIS\MemSize = #PacketSize
    EndIf
    *THIS\Pointer = *THIS\Address
    *THIS\Length  = 0
    CompilerIf #DebugCountUsed : *THIS\Used = 0 : CompilerEndIf
                  
  EndProcedure
  
  Procedure AddES(*THIS.ST_ExString, String.s)
    ProcedureReturnIf(*THIS\Address = 0)
    Protected Length = StringByteLength(String), Result = #True
    ProcedureReturnIf(Length < 1)
    
    Result = CheckResizeMemory(*THIS, Length)
    If Result
      CopyMemoryString(@String, @*THIS\Pointer)
      *THIS\Length + Length
      CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure InsertES(*THIS.ST_ExString, String.s, Position = 1)
    ProcedureReturnIf(*THIS\Address = 0 Or Position < 1)
    Protected Length = StringByteLength(String), Result = #True
    ProcedureReturnIf(Length < 1)
    
    Result = CheckResizeMemory(*THIS, Length)
    If Result
      Position - 1
      Position * SizeOf(Character)
      If Position > *THIS\Length
        Position = *THIS\Length
      ElseIf Position < *THIS\Length
        MoveMemory(*THIS\Address + Position, *THIS\Address + Length + Position, *THIS\Length - Position)
      EndIf
      CopyMemory(@String, *THIS\Address + Position, Length)
      *THIS\Pointer + Length
      *THIS\Length  + Length
      CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure ConcatES(*THIS.ST_ExString, *THISAdd.ST_ExString)
    ProcedureReturnIf(*THIS\Address = 0 Or *THISAdd\Address = 0)
    Protected Result = #True
    
    Result = CheckResizeMemory(*THIS, *THISAdd\Length)
    If Result
      CopyMemory(*THISAdd\Address, *THIS\Pointer, *THISAdd\Length)
      *THIS\Pointer + *THISAdd\Length
      *THIS\Length  + *THISAdd\Length
      CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    EndIf
    
    ProcedureReturn Result
  EndProcedure
  
  Procedure LenES(*THIS.ST_ExString)
    ProcedureReturnIf(*THIS\Address = 0)
    
    ProcedureReturn *THIS\Length / SizeOf(Character)
  EndProcedure
  
    Procedure UsedES(*THIS.ST_ExString)
      ProcedureReturnIf(*THIS\Address = 0)
      
      CompilerIf #DebugCountUsed
        ProcedureReturn *THIS\Used
      CompilerElse
        ProcedureReturn 0
      CompilerEndIf
    EndProcedure
  
  Procedure.s GetES(*THIS.ST_ExString)
    ProcedureReturnSIf(*THIS\Address = 0)
    
    ProcedureReturn PeekS(*THIS\Address, *THIS\Length / SizeOf(Character))
  EndProcedure
  
  Procedure.s LeftES(*THIS.ST_ExString, Length)
    ProcedureReturnSIf(*THIS\Address = 0 Or Length < 1)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Length > *THIS\Length / SizeOf(Character)
      Length = *THIS\Length / SizeOf(Character)
    EndIf
    
    ProcedureReturn PeekS(*THIS\Address, Length)
  EndProcedure
  
  Procedure.s RightES(*THIS.ST_ExString, Length)
    ProcedureReturnSIf(*THIS\Address = 0 Or Length < 1)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Length > *THIS\Length / SizeOf(Character)
      Length = *THIS\Length / SizeOf(Character)
    EndIf
    
    ProcedureReturn PeekS(*THIS\Pointer - (Length * SizeOf(Character)), Length)
  EndProcedure
  
  Procedure.s MidES(*THIS.ST_ExString, Position, Length = #PB_Ignore)
    ProcedureReturnSIf(*THIS\Address = 0 Or Position < 1)
    Position - 1
    Position * SizeOf(Character)
    ProcedureReturnSIf(Position >= *THIS\Length)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Length = #PB_Ignore Or (Length * SizeOf(Character) + Position > *THIS\Length)
      Length = (*THIS\Length - Position) / SizeOf(Character)
    EndIf
    
    ProcedureReturn PeekS(*THIS\Address + Position, Length)
  EndProcedure
  
  Procedure.s LSetES(*THIS.ST_ExString, Length, Char.s = " ", Flag = #PB_Default)   ; ; Flag = #PB_Default | #PB_String_InPlace
    ProcedureReturnSIf(*THIS\Address = 0 Or Length < 1)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    Length * SizeOf(Character)
    If Length <= *THIS\Length
      If Flag = #PB_String_InPlace
        *THIS\Length = Length
        *THIS\Pointer = *THIS\Address + Length
      EndIf
      ProcedureReturn PeekS(*THIS\Address, Length  / SizeOf(Character))
    EndIf
    
    Protected *String
    If Flag = #PB_String_InPlace
      If CheckResizeMemory(*THIS, Length - *THIS\Length)
        *String = *THIS\Address
      EndIf
    Else
      *String = AllocateMemory(Length)
      If *String
        CopyMemory(*THIS\Address, *String, *THIS\Length)
      EndIf
    EndIf
    
    If *String
      Protected *Char.Character = @Char
      FillMemory(*String + *THIS\Length, Length - *THIS\Length, *Char\c, #PB_Character)

      If Flag = #PB_String_InPlace
        *THIS\Length  = Length
        *THIS\Pointer = *THIS\Address + Length
        ProcedureReturn PeekS(*String, Length / SizeOf(Character))
      Else  
        Protected String.s = PeekS(*String, Length / SizeOf(Character))
        FreeMemory(*String)
        ProcedureReturn String
      EndIf
    EndIf
    
  EndProcedure
  
  Procedure.s RSetES(*THIS.ST_ExString, Length, Char.s = " ", Flag = #PB_Default)   ; Flag = #PB_Default | #PB_String_InPlace
    ProcedureReturnSIf(*THIS\Address = 0 Or Length < 1)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    Length * SizeOf(Character)
    If Length <= *THIS\Length
      If Flag = #PB_String_InPlace
        *THIS\Length  = Length
        *THIS\Pointer = *THIS\Address + Length
      EndIf
      ProcedureReturn PeekS(*THIS\Address, Length / SizeOf(Character))
    EndIf
    
    Protected *String
    If Flag = #PB_String_InPlace
      If CheckResizeMemory(*THIS, Length - *THIS\Length)
        *String = *THIS\Address
        MoveMemory(*THIS\Address, *THIS\Address + Length - *THIS\Length, *THIS\Length)
      EndIf
    Else
      *String = AllocateMemory(Length)
      If *String
        CopyMemory(*THIS\Address, *String + Length - *THIS\Length, *THIS\Length)
      EndIf
    EndIf
    
    If *String
      Protected *Char.Character = @Char
      FillMemory(*String, Length - *THIS\Length, *Char\c, #PB_Character)
      
      If Flag = #PB_String_InPlace
        *THIS\Length  = Length
        *THIS\Pointer = *THIS\Address + Length
        ProcedureReturn PeekS(*String, Length / SizeOf(Character))
      Else
        Protected String.s = PeekS(*String, Length / SizeOf(Character))
        FreeMemory(*String)
        ProcedureReturn String
      EndIf
    EndIf
    
  EndProcedure
  
  Procedure.s LCutES(*THIS.ST_ExString, Length, Flag = #PB_Default)   ; Length = Specifies how many characters to delete on the left, Flag = #PB_Default | #PB_String_InPlace
    ProcedureReturnSIf(*THIS\Address = 0)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Length < 1
      ProcedureReturn PeekS(*THIS\Address, *THIS\Length / SizeOf(Character))
    ElseIf Length >= *THIS\Length / SizeOf(Character)
      If Flag = #PB_String_InPlace
        ResetES(*THIS)
      EndIf
      ProcedureReturn
    EndIf
    
    Protected *String
    Length * SizeOf(Character)
    If Flag = #PB_String_InPlace
      *String = *THIS\Address
      MoveMemory(*THIS\Address + Length, *THIS\Address, *THIS\Length - Length)
    Else
      *String = AllocateMemory(*THIS\Length - Length)
      If *String
        CopyMemory(*THIS\Address + Length, *String, *THIS\Length - Length)
      EndIf
    EndIf
    
    If *String
      If Flag = #PB_String_InPlace
        *THIS\Pointer - Length
        *THIS\Length  - Length
        ProcedureReturn PeekS(*String, *THIS\Length / SizeOf(Character))
      Else  
        Protected String.s = PeekS(*String, (*THIS\Length - Length) / SizeOf(Character))
        FreeMemory(*String)
        ProcedureReturn String
      EndIf
    EndIf
    
  EndProcedure
  
  Procedure.s RCutES(*THIS.ST_ExString, Length, Flag = #PB_Default)   ; Length = Specifies how many characters to delete on the right, Flag = #PB_Default | #PB_String_InPlace
    ProcedureReturnSIf(*THIS\Address = 0)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Length < 1
      ProcedureReturn PeekS(*THIS\Address, *THIS\Length / SizeOf(Character))
    ElseIf Length >= *THIS\Length / SizeOf(Character)
      If Flag = #PB_String_InPlace
        ResetES(*THIS)
      EndIf
      ProcedureReturn
    EndIf
    
    Protected *String
    Length * SizeOf(Character)
    If Flag = #PB_String_InPlace
      *String = *THIS\Address
    Else
      *String = AllocateMemory(*THIS\Length - Length)
      If *String
        CopyMemory(*THIS\Address, *String, *THIS\Length - Length)
      EndIf
    EndIf
    
    If *String
      If Flag = #PB_String_InPlace
        *THIS\Pointer - Length
        *THIS\Length  - Length
        ProcedureReturn PeekS(*String, *THIS\Length / SizeOf(Character))
      Else  
        Protected String.s = PeekS(*String, (*THIS\Length - Length) / SizeOf(Character))
        FreeMemory(*String)
        ProcedureReturn String
      EndIf
    EndIf
    
  EndProcedure
  
  Procedure.s LCaseES(*THIS.ST_ExString, Flag = #PB_Default)   ; Flag = #PB_Default | #PB_String_InPlace
    ProcedureReturnSIf(*THIS\Address = 0)
    Protected String.s, *memString
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Flag = #PB_String_InPlace
      *memString = *THIS\Address
    Else
      *memString = AllocateMemory(*THIS\Length)
      If *memString
        CopyMemory(*THIS\Address, *memString, *THIS\Length)
      EndIf
    EndIf
    
    If *memString
      Protected *String.Character = *memString
      While *String\c <> 0 
        Select *String\c
          Case 65 To 90
            *String\c + 32
          Case 192 To 222
            If Not *String\c = 208 And Not *String\c = 215
              *String\c + 32
            EndIf
        EndSelect
        *String + SizeOf(Character)
      Wend
      If Flag = #PB_String_InPlace
        ProcedureReturn PeekS(*memString, *THIS\Length / SizeOf(Character))
      Else  
        String = PeekS(*memString, *THIS\Length / SizeOf(Character))
        FreeMemory(*memString)
        ProcedureReturn String
      EndIf
    EndIf
    
  EndProcedure
  
  Procedure.s UCaseES(*THIS.ST_ExString, Flag = #PB_Default)   ; Flag = #PB_Default | #PB_String_InPlace
    ProcedureReturnSIf(*THIS\Address = 0)
    Protected String.s, *memString = AllocateMemory(*THIS\Length)
    
    CompilerIf #DebugCountUsed : *THIS\Used + 1 : CompilerEndIf
    If Flag = #PB_String_InPlace
      *memString = *THIS\Address
    Else
      *memString = AllocateMemory(*THIS\Length)
      If *memString
        CopyMemory(*THIS\Address, *memString, *THIS\Length)
      EndIf
    EndIf
    
    If *memString
      Protected *String.Character = *memString
      While *String\c <> 0 
        Select *String\c
          Case 97 To 122
            *String\c - 32
          Case 224 To 254
            If Not *String\c = 240 And Not *String\c = 247
              *String\c - 32
            EndIf
        EndSelect
        *String + SizeOf(Character)
      Wend
      If Flag = #PB_String_InPlace
        ProcedureReturn PeekS(*memString, *THIS\Length / SizeOf(Character))
      Else  
        String = PeekS(*memString, *THIS\Length / SizeOf(Character))
        FreeMemory(*memString)
        ProcedureReturn String
      EndIf
    EndIf
    
  EndProcedure
  
  Procedure FreeES(*THIS.ST_ExString)
    ProcedureReturnIf(*THIS\Address = 0)
    
    FreeMemory(*THIS\Address)
    ClearStructure(*THIS, ST_ExString)
  EndProcedure
  
EndModule

;- ----- Test ExString vs PB-Way -----
CompilerIf #PB_Compiler_IsMainFile
  EnableExplicit
  
  UseModule ExString   ; to access the module from outside, otherwise use ExString:: 
  
  Define ID1 = NewES()
  
  CompilerIf Not #PB_Compiler_Debugger
    Define OrgString.s = "abcdefghijklmnopqrstuvwxyz" + #CRLF$
    Define I, NewString.s, s1, s2
    
    ; ExString Way
    s1  = ElapsedMilliseconds()
    For I = 1 To 10000
      AddES(ID1, OrgString)
    Next I
    s1 = ElapsedMilliseconds() - s1
    
    ; PB Way
    s2  = ElapsedMilliseconds()
    For I = 1 To 10000
      NewString + OrgString
    Next I
    s2 = ElapsedMilliseconds() - s2
    
    MessageRequester("Info", ~"ExString: \t" + Str(s1) + ~"ms\nPB-Way: \t\t" + Str(s2) + "ms")
      
    FreeES(ID1)
    
  CompilerElse
    Define OrgString.s = "Hello"
    
    ; Get and Len ExString
    Debug ~"Add String \"Hello\" in ExString ID1" 
    AddES(ID1, OrgString)
    Debug "Get ID1 = " + GetES(ID1)
    Debug "Len ID1 = " + Str(LenES(ID1))
    Debug ""
    
    ; Concat ExString
    Define ID2 = NewES()
    
    Debug ~"Add String \" World\" in ExString ID2"
    AddES(ID2, " World")
    Debug "Get ID2 = " + GetES(ID2)
    ConcatES(ID1, ID2)
    FreeES(ID2)
    Debug "Concat ID1 + ID2 = " + GetES(ID1)
    Debug ""
    
    ; Insert ExString
    Debug "Continue with ID1 = " + GetES(ID1)
    InsertES(ID1, "* ")
    Debug ~"Insert \"* \" at beginning = " + GetES(ID1)
    InsertES(ID1, "- ", 9)
    Debug ~"Insert \"- \" at pos 9 = " + GetES(ID1)
    InsertES(ID1, " ! ", 16)
    Debug ~"Insert \" ! \" at pos 16 = " + GetES(ID1)
    Debug ""
    
    ; LCase and UCase ExString
    Debug "LCase = " + LCaseES(ID1)                                                  ; Flag = #PB_Default | #PB_String_InPlace
    Debug "UCase = " + UCaseES(ID1)                                                  ; Flag = #PB_Default | #PB_String_InPlace
    Debug ""
    
    ; Left, Right and Mid ExString
    Debug "Left, Len 7 = " + LeftES(ID1, 7)
    Debug "Right, Len 8 = " + RightES(ID1, 8)
    Debug "Mid, Pos 3, Len 13 = " + MidES(ID1, 3, 13)
    Debug ""
    
    ; LSet and RSet ExString
    Debug ~"LSet, Len 21, Char \"*\" = " + LSetES(ID1, 21, "*", #PB_String_InPlace)  ; Flag = #PB_Default | #PB_String_InPlace
    Debug ~"RSet, Len 23, Char \"*\" = " + RSetES(ID1, 23, "*", #PB_String_InPlace)  ; Flag = #PB_Default | #PB_String_InPlace
    
    Debug ~"LCut, Len 4 = " + LCutES(ID1, 4, #PB_String_InPlace)  ; Flag = #PB_Default | #PB_String_InPlace
    Debug ~"RCut, Len 4 = " + RCutES(ID1, 4, #PB_String_InPlace)  ; Flag = #PB_Default | #PB_String_InPlace
    
    FreeES(ID1)
    
  CompilerEndIf
  
CompilerEndIf
Last edited by ChrisR on Tue Feb 21, 2023 4:12 pm, edited 2 times in total.
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: ExString for fast handling of large strings

Post by ChrisR »

Add Flag = #PB_Default | #PB_String_InPlace for the functions LSet, RSet, LCase, UCase
#PB_String_InPlace allow to apply the changes directly in the original string, in addition to the returned string.
And it's faster with #PB_String_InPlace, without the need to allocate memory for the result string

For info, if needed, the additional functions added here could be integrated, without too much difficulty in StringBuilder Interface.
It works the same way and uses ~the same structure ( *Address, *Pointer, Length). Copy/Paste the procedure + Interfaces should be enough.
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: ExString for fast handling of large strings

Post by ChrisR »

I have updated with:

A constant: #DebugCountUsed = #False ; #True | #False (and 1 additional field "Used.i" in ExString Structure, if the constant is enabled)
It allows in dev, in debug, to know the number of times the ExString is called.
With it + the length, it should gives an indication to choose between an ExString or native PB strings.

And 2 functions:
LCutES(ExString, Length, Flag = #PB_Default) ; Length = Specifies how many characters to delete on the left, Flag = #PB_Default | #PB_String_InPlace
RCutES(ExString, Length, Flag = #PB_Default) ; Length = Specifies how many characters to delete on the right, Flag = #PB_Default | #PB_String_InPlace


To save time, I added several ExString in IceDesign and I compared the performances with a loop on the code generation, on 2 forms examples, it gives:
14 ms vs 72 ms: for an interface with 1 Panel, 10 tabs and 236 Gadgets and all settings options enabled: Resize All, Bind All Gadgets in an Include File, MultiLanguage,...
4 ms vs 6 ms: for an interface with 1 Panel, 3 tabs and 20 Gadgets and all settings options enabled
User avatar
Kwai chang caine
Always Here
Always Here
Posts: 5494
Joined: Sun Nov 05, 2006 11:42 pm
Location: Lyon - France

Re: ExString for fast handling of large strings

Post by Kwai chang caine »

That works apparently here and can be very usefull :wink:
Thanks for sharing 8)
ImageThe happiness is a road...
Not a destination
User avatar
ChrisR
Addict
Addict
Posts: 1466
Joined: Sun Jan 08, 2017 10:27 pm
Location: France

Re: ExString for fast handling of large strings

Post by ChrisR »

With PureBasic 6.20 out and the ability to compile our own user Libraries now, I've modified the code accordingly to create the ExString user Library for PureBasic 6.20 and above.

I used the great pf shadoko's Lib - PB 6.20 tool and then slightly adapted the QuickHelps

To Compile the ExString user library: PureBasic-CLI.cmd > pbcompilerc xxx\ExString.pb /OPTIMIZER /PURELIBRARY /OUTPUT ExString

Code: Select all

;- Top

DisablePureLibrary ExString

; -----------------------------------------------------------------------------
;  Name       : ExString library
;  Description: Concatenation and manipulation of large strings from the pointer in memory. With a huge gain in speed for large strings vs PB.
;               It avoids the bottleneck in PB to get the string length with the null character that indicates the end of the string.
;  Compile Lib: Compile the ExString user library for PureBasic 6.20 and above:
;                  PureBasic-CLI.cmd > pbcompilerc xxx\ExString.pb /OPTIMIZER /PURELIBRARY /OUTPUT ExString
;  Target-OS  : Cross-platform
;  
;  Based on   : StringBuilder
;  Author     : HeX0R
;  Date       : 2020-08-03
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?p=558277#p558277
;  Updated    : 2023-02-16 use an interface now for easier handling
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?t=80831
;  
;  Based on   : Based on FastString - Structured Memory String
;  Author     : mk-soft
;  Date       : 2020-07-25
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?t=75758
;
;  Renamed    : Renamed the module to ExString to avoid any confusion with the HeX0R's original module
;  Change     : Remove the Ex String List
;               Additional functions (with Prefix ES here): Add, Insert, Concat, Reset, Len, Get Left, Right, Mid, LSet, RSet, LCase, UCase
;  Author     : ChrisR
;  Date       : 2023-02-16
;  Forum      : https://www.purebasic.fr/english/viewtopic.php?t=80832
; -----------------------------------------------------------------------------
; Functions:
; NewES()                                                        - Returns the address of the new ExString (Extended management for large Strings)
; ResetES(*ExString[, KeepMemory = #False])                      - Reset ExString
; AddES(*ExString, String.s)                                     - Add a string to the end of ExString
; InsertES(*ExString, String.s[, Position = 1])                  - Insert a string in ExString at the desired position
; ConcatES(*ExString, *ExStringAdd)                              - Concatenate two ExStrings
; LenES(*ExString)                                               - Return the character length of ExString
; GetES(*ExString)                                               - Returns the string from ExString
; LeftES(*ExString, Length)                                      - Returns a string holding the specified number of characters from the left side of ExString
; RightES(*ExString, Length)                                     - Returns a string holding the specified number of characters from the right side of ExString
; MidES(*ExString, Position[, Length = #PB_Ignore])              - Returns a string holding the extracted number of ExString
; LSetES(*ExString, Length[, Char.s = " "[, Flag = #PB_Default]) - Returns a string holding ExString padded with the specified character at the end to fit the length (Flag = #PB_String_InPlace)
; RSetES(*ExString, Length[, Char.s = " "[, Flag = #PB_Default]) - Returns a string holding ExString padded With the specified character at the beginning To fit the length (Flag = #PB_String_InPlace)
; LCutES(*ExString, Length[, Flag = #PB_Default])                - Returns a string with the number of characters to be deleted to the left of ExString (Flag = #PB_String_InPlace)
; RCutES(*ExString, Length[, Flag = #PB_Default])                - Returns a string with the number of characters to be deleted to the right of ExString (Flag = #PB_String_InPlace)
; LCaseES(*ExString[, Flag = #PB_Default])                       - Returns a string converted into lower case characters of ExString (Flag = #PB_String_InPlace)
; UCaseES(*ExString[, Flag = #PB_Default])                       - Returns a string converted into upper case characters of ExString (Flag = #PB_String_InPlace)
; FreeES(*ExString)                                              - Free the given ExString, previously created by NewES()
; -----------------------------------------------------------------------------

EnableExplicit

#PacketSize = 8192   ; 65536

Structure ExStringST
  *Address
  *Pointer
  MemSize.i
  Length.i
EndStructure

Macro ProcedureReturnIf(Cond, ReturnVal = 0)
  If Cond
    ProcedureReturn ReturnVal
  EndIf
EndMacro

Macro ProcedureReturnSIf(Cond)
  If Cond
    ProcedureReturn
  EndIf
EndMacro

Procedure CheckResizeMemoryES(*ExString.ExStringST, Length)
  Protected *Add, Result = #True
  
  While Length + *ExString\Length + SizeOf(Character) > *ExString\MemSize
    *ExString\MemSize + #PacketSize
    *Add = ReAllocateMemory(*ExString\Address, *ExString\MemSize, #PB_Memory_NoClear)
    If *Add
      If *Add <> *ExString\Address
        *ExString\Pointer = *ExString\Pointer - *ExString\Address + *Add
        *ExString\Address = *Add
      EndIf
    Else
      Result = #False
      Break
    EndIf
  Wend
  
  ProcedureReturn Result
EndProcedure

; QuickHelp NewES() - Returns the address of the new ExString (Extended management for large Strings)
ProcedureDLL NewES()
  Protected *Buffer, Result
  
  *Buffer = AllocateMemory(#PacketSize, #PB_Memory_NoClear)
  If *Buffer
    Protected *ExString.ExStringST
    *ExString = AllocateStructure(ExStringST)
    *ExString\Address     = *Buffer
    *ExString\Pointer     = *Buffer
    *ExString\MemSize     = #PacketSize
    Result           = *ExString
  EndIf
  
  ProcedureReturn Result
EndProcedure

; QuickHelp ResetES(*ExString[, KeepMemory = #False]) - Reset ExString
ProcedureDLL ResetES2(*ExString.ExStringST, KeepMemory)
  ProcedureReturnIf(*ExString\Address = 0)
  
  If KeepMemory = #False And *ExString\MemSize > #PacketSize
    *ExString\Address = ReAllocateMemory(*ExString\Address, #PacketSize, #PB_Memory_NoClear)
    *ExString\MemSize = #PacketSize
  EndIf
  *ExString\Pointer = *ExString\Address
  *ExString\Length  = 0
  
EndProcedure

ProcedureDLL ResetES(*ExString.ExStringST)
  ProcedureReturn ResetES2(*ExString.ExStringST,#False)
EndProcedure

; QuickHelp AddES(*ExString, String.s) - Add a string to the end of ExString
ProcedureDLL AddES(*ExString.ExStringST, String.s)
  ProcedureReturnIf(*ExString\Address = 0)
  Protected Length = StringByteLength(String), Result = #True
  ProcedureReturnIf(Length < 1)
  
  Result = CheckResizeMemoryES(*ExString, Length)
  If Result
    CopyMemoryString(@String, @*ExString\Pointer)
    *ExString\Length + Length
  EndIf
  
  ProcedureReturn Result
EndProcedure

; QuickHelp InsertES(*ExString, String.s[, Position = 1]) - Insert a string in ExString at the desired position
ProcedureDLL InsertES2(*ExString.ExStringST, String.s, Position)
  ProcedureReturnIf(*ExString\Address = 0 Or Position < 1)
  Protected Length = StringByteLength(String), Result = #True
  ProcedureReturnIf(Length < 1)
  
  Result = CheckResizeMemoryES(*ExString, Length)
  If Result
    Position - 1
    Position * SizeOf(Character)
    If Position > *ExString\Length
      Position = *ExString\Length
    ElseIf Position < *ExString\Length
      MoveMemory(*ExString\Address + Position, *ExString\Address + Length + Position, *ExString\Length - Position)
    EndIf
    CopyMemory(@String, *ExString\Address + Position, Length)
    *ExString\Pointer + Length
    *ExString\Length  + Length
  EndIf
  
  ProcedureReturn Result
EndProcedure

ProcedureDLL InsertES(*ExString.ExStringST,String.s)
  ProcedureReturn InsertES2(*ExString.ExStringST,String.s,1)
EndProcedure

; QuickHelp ConcatES(*ExString, *ExStringAdd) - Concatenate two ExStrings
ProcedureDLL ConcatES(*ExString.ExStringST, *ExStringAdd.ExStringST)
  ProcedureReturnIf(*ExString\Address = 0 Or *ExStringAdd\Address = 0)
  Protected Result = #True
  
  Result = CheckResizeMemoryES(*ExString, *ExStringAdd\Length)
  If Result
    CopyMemory(*ExStringAdd\Address, *ExString\Pointer, *ExStringAdd\Length)
    *ExString\Pointer + *ExStringAdd\Length
    *ExString\Length  + *ExStringAdd\Length
  EndIf
  
  ProcedureReturn Result
EndProcedure

; QuickHelp LenES(*ExString) - Return the character length of ExString
ProcedureDLL LenES(*ExString.ExStringST)
  ProcedureReturnIf(*ExString\Address = 0)
  
  ProcedureReturn *ExString\Length / SizeOf(Character)
EndProcedure

; QuickHelp GetES(*ExString) - Returns the string from ExString
ProcedureDLL.s GetES(*ExString.ExStringST)
  ProcedureReturnSIf(*ExString\Address = 0)
  
  ProcedureReturn PeekS(*ExString\Address, *ExString\Length / SizeOf(Character))
EndProcedure

; QuickHelp LeftES(*ExString, Length) - Returns a string holding the specified number of characters from the left side of ExString
ProcedureDLL.s LeftES(*ExString.ExStringST, Length)
  ProcedureReturnSIf(*ExString\Address = 0 Or Length < 1)
  
  If Length > *ExString\Length / SizeOf(Character)
    Length = *ExString\Length / SizeOf(Character)
  EndIf
  
  ProcedureReturn PeekS(*ExString\Address, Length)
EndProcedure

; QuickHelp RightES(*ExString, Length) - Returns a string holding the specified number of characters from the right side of ExString
ProcedureDLL.s RightES(*ExString.ExStringST, Length)
  ProcedureReturnSIf(*ExString\Address = 0 Or Length < 1)
  
  If Length > *ExString\Length / SizeOf(Character)
    Length = *ExString\Length / SizeOf(Character)
  EndIf
  
  ProcedureReturn PeekS(*ExString\Pointer - (Length * SizeOf(Character)), Length)
EndProcedure

; QuickHelp MidES(*ExString, Position[, Length = #PB_Ignore]) - Returns a string holding the extracted number of ExString
ProcedureDLL.s MidES2(*ExString.ExStringST, Position, Length)
  ProcedureReturnSIf(*ExString\Address = 0 Or Position < 1)
  Position - 1
  Position * SizeOf(Character)
  ProcedureReturnSIf(Position >= *ExString\Length)
  
  If Length = #PB_Ignore Or (Length * SizeOf(Character) + Position > *ExString\Length)
    Length = (*ExString\Length - Position) / SizeOf(Character)
  EndIf
  
  ProcedureReturn PeekS(*ExString\Address + Position, Length)
EndProcedure

ProcedureDLL.s MidES(*ExString.ExStringST,Position)
  ProcedureReturn MidES2(*ExString.ExStringST,Position,#PB_Ignore)
EndProcedure

; QuickHelp LSetES(*ExString, Length[, Char.s = " "[, Flag = #PB_Default]) - Returns a string holding ExString padded with the specified character at the end to fit the length (Flag = #PB_String_InPlace)
ProcedureDLL.s LSetES3(*ExString.ExStringST, Length, Char.s, Flag)
  ProcedureReturnSIf(*ExString\Address = 0 Or Length < 1)
  
  Length * SizeOf(Character)
  If Length <= *ExString\Length
    If Flag = #PB_String_InPlace
      *ExString\Length = Length
      *ExString\Pointer = *ExString\Address + Length
    EndIf
    ProcedureReturn PeekS(*ExString\Address, Length  / SizeOf(Character))
  EndIf
  
  Protected *String
  If Flag = #PB_String_InPlace
    If CheckResizeMemoryES(*ExString, Length - *ExString\Length)
      *String = *ExString\Address
    EndIf
  Else
    *String = AllocateMemory(Length)
    If *String
      CopyMemory(*ExString\Address, *String, *ExString\Length)
    EndIf
  EndIf
  
  If *String
    Protected *Char.Character = @Char
    FillMemory(*String + *ExString\Length, Length - *ExString\Length, *Char\c, #PB_Character)
    
    If Flag = #PB_String_InPlace
      *ExString\Length  = Length
      *ExString\Pointer = *ExString\Address + Length
      ProcedureReturn PeekS(*String, Length / SizeOf(Character))
    Else  
      Protected String.s = PeekS(*String, Length / SizeOf(Character))
      FreeMemory(*String)
      ProcedureReturn String
    EndIf
  EndIf
  
EndProcedure

ProcedureDLL.s LSetES2(*ExString.ExStringST,Length,Char.s)
  ProcedureReturn LSetES3(*ExString.ExStringST,Length,Char.s,#PB_Default)
EndProcedure

ProcedureDLL.s LSetES(*ExString.ExStringST,Length)
  ProcedureReturn LSetES3(*ExString.ExStringST,Length,"",#PB_Default)
EndProcedure

; QuickHelp RSetES(*ExString, Length[, Char.s = " "[, Flag = #PB_Default]) - Returns a string holding ExString padded with the specified character at the beginning to fit the length (Flag = #PB_String_InPlace)
ProcedureDLL.s RSetES3(*ExString.ExStringST, Length, Char.s , Flag)
  ProcedureReturnSIf(*ExString\Address = 0 Or Length < 1)
  
  Length * SizeOf(Character)
  If Length <= *ExString\Length
    If Flag = #PB_String_InPlace
      *ExString\Length  = Length
      *ExString\Pointer = *ExString\Address + Length
    EndIf
    ProcedureReturn PeekS(*ExString\Address, Length / SizeOf(Character))
  EndIf
  
  Protected *String
  If Flag = #PB_String_InPlace
    If CheckResizeMemoryES(*ExString, Length - *ExString\Length)
      *String = *ExString\Address
      MoveMemory(*ExString\Address, *ExString\Address + Length - *ExString\Length, *ExString\Length)
    EndIf
  Else
    *String = AllocateMemory(Length)
    If *String
      CopyMemory(*ExString\Address, *String + Length - *ExString\Length, *ExString\Length)
    EndIf
  EndIf
  
  If *String
    Protected *Char.Character = @Char
    FillMemory(*String, Length - *ExString\Length, *Char\c, #PB_Character)
    
    If Flag = #PB_String_InPlace
      *ExString\Length  = Length
      *ExString\Pointer = *ExString\Address + Length
      ProcedureReturn PeekS(*String, Length / SizeOf(Character))
    Else
      Protected String.s = PeekS(*String, Length / SizeOf(Character))
      FreeMemory(*String)
      ProcedureReturn String
    EndIf
  EndIf
  
EndProcedure

ProcedureDLL.s RSetES2(*ExString.ExStringST,Length,Char.s)
  ProcedureReturn RSetES3(*ExString.ExStringST,Length,Char.s,#PB_Default)
EndProcedure

ProcedureDLL.s RSetES(*ExString.ExStringST,Length)
  ProcedureReturn RSetES3(*ExString.ExStringST,Length,"",#PB_Default)
EndProcedure

; QuickHelp LCutES(*ExString, Length[, Flag = #PB_Default]) - Returns a string with the number of characters to be deleted to the left of ExString (Flag = #PB_String_InPlace)
ProcedureDLL.s LCutES2(*ExString.ExStringST, Length, Flag)
  ProcedureReturnSIf(*ExString\Address = 0)
  
  If Length < 1
    ProcedureReturn PeekS(*ExString\Address, *ExString\Length / SizeOf(Character))
  ElseIf Length >= *ExString\Length / SizeOf(Character)
    If Flag = #PB_String_InPlace
      ResetES2(*ExString, #False)
    EndIf
    ProcedureReturn
  EndIf
  
  Protected *String
  Length * SizeOf(Character)
  If Flag = #PB_String_InPlace
    *String = *ExString\Address
    MoveMemory(*ExString\Address + Length, *ExString\Address, *ExString\Length - Length)
  Else
    *String = AllocateMemory(*ExString\Length - Length)
    If *String
      CopyMemory(*ExString\Address + Length, *String, *ExString\Length - Length)
    EndIf
  EndIf
  
  If *String
    If Flag = #PB_String_InPlace
      *ExString\Pointer - Length
      *ExString\Length  - Length
      ProcedureReturn PeekS(*String, *ExString\Length / SizeOf(Character))
    Else  
      Protected String.s = PeekS(*String, (*ExString\Length - Length) / SizeOf(Character))
      FreeMemory(*String)
      ProcedureReturn String
    EndIf
  EndIf
  
EndProcedure

ProcedureDLL.s LCutES(*ExString.ExStringST,Length)
  ProcedureReturn LCutES2(*ExString.ExStringST,Length,#PB_Default)
EndProcedure

; QuickHelp RCutES(*ExString, Length[, Flag = #PB_Default]) - Returns a string with the number of characters to be deleted to the right of ExString (Flag = #PB_String_InPlace)
ProcedureDLL.s RCutES2(*ExString.ExStringST, Length, Flag)
  ProcedureReturnSIf(*ExString\Address = 0)
  
  If Length < 1
    ProcedureReturn PeekS(*ExString\Address, *ExString\Length / SizeOf(Character))
  ElseIf Length >= *ExString\Length / SizeOf(Character)
    If Flag = #PB_String_InPlace
      ResetES2(*ExString, #False)
    EndIf
    ProcedureReturn
  EndIf
  
  Protected *String
  Length * SizeOf(Character)
  If Flag = #PB_String_InPlace
    *String = *ExString\Address
  Else
    *String = AllocateMemory(*ExString\Length - Length)
    If *String
      CopyMemory(*ExString\Address, *String, *ExString\Length - Length)
    EndIf
  EndIf
  
  If *String
    If Flag = #PB_String_InPlace
      *ExString\Pointer - Length
      *ExString\Length  - Length
      ProcedureReturn PeekS(*String, *ExString\Length / SizeOf(Character))
    Else  
      Protected String.s = PeekS(*String, (*ExString\Length - Length) / SizeOf(Character))
      FreeMemory(*String)
      ProcedureReturn String
    EndIf
  EndIf
  
EndProcedure

ProcedureDLL.s RCutES(*ExString.ExStringST,Length)
  ProcedureReturn RCutES2(*ExString.ExStringST,Length,#PB_Default)
EndProcedure

; QuickHelp LCaseES(*ExString[, Flag = #PB_Default]) - Returns a string converted into lower case characters of ExString (Flag = #PB_String_InPlace)
ProcedureDLL.s LCaseES2(*ExString.ExStringST, Flag)
  ProcedureReturnSIf(*ExString\Address = 0)
  Protected String.s, *memString
  
  If Flag = #PB_String_InPlace
    *memString = *ExString\Address
  Else
    *memString = AllocateMemory(*ExString\Length)
    If *memString
      CopyMemory(*ExString\Address, *memString, *ExString\Length)
    EndIf
  EndIf
  
  If *memString
    Protected *String.Character = *memString
    While *String\c <> 0 
      Select *String\c
        Case 65 To 90
          *String\c + 32
        Case 192 To 222
          If Not *String\c = 208 And Not *String\c = 215
            *String\c + 32
          EndIf
      EndSelect
      *String + SizeOf(Character)
    Wend
    If Flag = #PB_String_InPlace
      ProcedureReturn PeekS(*memString, *ExString\Length / SizeOf(Character))
    Else  
      String = PeekS(*memString, *ExString\Length / SizeOf(Character))
      FreeMemory(*memString)
      ProcedureReturn String
    EndIf
  EndIf
  
EndProcedure

ProcedureDLL.s LCaseES(*ExString.ExStringST)
  ProcedureReturn LCaseES2(*ExString.ExStringST,#PB_Default)
EndProcedure

; QuickHelp UCaseES(*ExString[, Flag = #PB_Default]) - Returns a string converted into upper case characters of ExString (Flag = #PB_String_InPlace)
ProcedureDLL.s UCaseES2(*ExString.ExStringST, Flag)
  ProcedureReturnSIf(*ExString\Address = 0)
  Protected String.s, *memString = AllocateMemory(*ExString\Length)
  
  If Flag = #PB_String_InPlace
    *memString = *ExString\Address
  Else
    *memString = AllocateMemory(*ExString\Length)
    If *memString
      CopyMemory(*ExString\Address, *memString, *ExString\Length)
    EndIf
  EndIf
  
  If *memString
    Protected *String.Character = *memString
    While *String\c <> 0 
      Select *String\c
        Case 97 To 122
          *String\c - 32
        Case 224 To 254
          If Not *String\c = 240 And Not *String\c = 247
            *String\c - 32
          EndIf
      EndSelect
      *String + SizeOf(Character)
    Wend
    If Flag = #PB_String_InPlace
      ProcedureReturn PeekS(*memString, *ExString\Length / SizeOf(Character))
    Else  
      String = PeekS(*memString, *ExString\Length / SizeOf(Character))
      FreeMemory(*memString)
      ProcedureReturn String
    EndIf
  EndIf
  
EndProcedure

ProcedureDLL.s UCaseES(*ExString.ExStringST)
  ProcedureReturn UCaseES2(*ExString.ExStringST,#PB_Default)
EndProcedure

; QuickHelp FreeES(*ExString) - Free the given ExString, previously created by NewES()
ProcedureDLL FreeES(*ExString.ExStringST)
  ProcedureReturnIf(*ExString\Address = 0)
  
  FreeMemory(*ExString\Address)
  ClearStructure(*ExString, ExStringST)
EndProcedure

Example for testing ExString Library, with and without the debugger

Code: Select all

;- ----- Test ExString Library vs PB-Way -----

Define ExStringID1 = NewES()

CompilerIf Not #PB_Compiler_Debugger
  Define String.s = "abcdefghijklmnopqrstuvwxyz" + #CRLF$
  Define NewString.s, I, s1, s2
  
  ; ExString Way
  s1  = ElapsedMilliseconds()
  For I = 1 To 10000
    AddES(ExStringID1, String)
  Next I
  NewString = GetES(ExStringID1)
  s1 = ElapsedMilliseconds() - s1
  
  ; PB Way
  s2  = ElapsedMilliseconds()
  For I = 1 To 10000
    NewString + String
  Next I
  s2 = ElapsedMilliseconds() - s2
  
  MessageRequester("Info", ~"ExString: \t" + Str(s1) + ~"ms\nPB-Way: \t\t" + Str(s2) + "ms")
  
  FreeES(ExStringID1)
  
CompilerElse
  Define String.s = "Hello"
  
  ; Get and Len ExString
  Debug ~"Add String \"Hello\" in ExString ExStringID1" 
  AddES(ExStringID1, String)
  Debug "Get ExStringID1 = " + GetES(ExStringID1)
  Debug "Len ExStringID1 = " + Str(LenES(ExStringID1))
  Debug ""
  
  ; Concat ExString
  Define ExStringID2 = NewES()
  
  Debug ~"Add String \" World\" in ExString ExStringID2"
  AddES(ExStringID2, " World")
  Debug "Get ExStringID2 = " + GetES(ExStringID2)
  ConcatES(ExStringID1, ExStringID2)
  FreeES(ExStringID2)
  Debug "Concat ExStringID1 + ExStringID2 = " + GetES(ExStringID1)
  Debug ""
  
  ; Insert ExString
  Debug "Continue with ExStringID1 = " + GetES(ExStringID1)
  InsertES(ExStringID1, "* ")
  Debug ~"Insert \"* \" at beginning = " + GetES(ExStringID1)
  InsertES(ExStringID1, "- ", 9)
  Debug ~"Insert \"- \" at pos 9 = " + GetES(ExStringID1)
  InsertES(ExStringID1, " ! ", 16)
  Debug ~"Insert \" ! \" at pos 16 = " + GetES(ExStringID1)
  Debug ""
  
  ; LCase and UCase ExString
  Debug "LCase = " + LCaseES(ExStringID1)                                                  ; Flag = #PB_Default | #PB_String_InPlace
  Debug "UCase = " + UCaseES(ExStringID1)                                                  ; Flag = #PB_Default | #PB_String_InPlace
  Debug ""
  
  ; Left, Right and Mid ExString
  Debug "Left, Len 7 = " + LeftES(ExStringID1, 7)
  Debug "Right, Len 8 = " + RightES(ExStringID1, 8)
  Debug "Mid, Pos 3, Len 13 = " + MidES(ExStringID1, 3, 13)
  Debug ""
  
  ; LSet and RSet ExString
  Debug ~"LSet, Len 21, Char \"*\" = " + LSetES(ExStringID1, 21, "*", #PB_String_InPlace)  ; Flag = #PB_Default | #PB_String_InPlace
  Debug ~"RSet, Len 23, Char \"*\" = " + RSetES(ExStringID1, 23, "*", #PB_String_InPlace)  ; Flag = #PB_Default | #PB_String_InPlace
  
  Debug ~"LCut, Len 4 = " + LCutES(ExStringID1, 4, #PB_String_InPlace)                     ; Flag = #PB_Default | #PB_String_InPlace
  Debug ~"RCut, Len 4 = " + RCutES(ExStringID1, 4, #PB_String_InPlace)                     ; Flag = #PB_Default | #PB_String_InPlace
  
  FreeES(ExStringID1)
  
CompilerEndIf
Post Reply