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

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