String Builder "Class"

Share your advanced PureBasic knowledge/code with the community.
Xombie
Addict
Addict
Posts: 898
Joined: Thu Jul 01, 2004 2:51 am
Location: Tacoma, WA
Contact:

String Builder "Class"

Post by Xombie »

Code updated For 5.20+

I've been using helpy's code for string building (viewtopic.php?t=12696&highlight=) but finally got frustrated when I could only call it once at a time. So I just ran myself through a quick course in pointers and, using froggerprogger's queue library as reference, created these string builder procedures that should allow you to use it any number of times. I also added a simple Length procedure.

Code: Select all

    Structure StringBuilder
      pString.i
      StringSize.i
      MemSize.i
      BlockSize.i
      InitDone.i
      ; Initialized to 0 (FALSE) at creation.  Means by default
      ; we have not initialized the structure/class.
    EndStructure

    Procedure sbCreate(BlockSize)
       ;
       Protected *sbClass.StringBuilder
       ;
       *sbClass = AllocateMemory(SizeOf(StringBuilder))
       ;
       If *sbClass\InitDone : FreeMemory(*sbClass\pString) : EndIf
       ; If the stringbuilder is already initialized, free the memory of the string.
       ; BlockSize min. 1024 ($400) Byte
       If BlockSize < $400 : BlockSize = $400 : EndIf
       ; BlockSize max. 1 MByte ($100000) Byte
       If BlockSize > $100000 : BlockSize = $100000 : EndIf
       ;
       If BlockSize <> (BlockSize & $FC00)
          BlockSize = (BlockSize & $FC00) + 1024
       EndIf
       ;
       *sbClass\BlockSize = BlockSize
       *sbClass\StringSize = 0
       *sbClass\pString = AllocateMemory(BlockSize)
       ; Allocate the memory needed for our string
       If *sbClass\pString <> 0
          ; Allocation went fine, let the structure know we initialized everything fine.
          *sbClass\InitDone = #True
          *sbClass\MemSize = *sbClass\BlockSize
          ; Set our memory size used to the size of the block.
       Else
          ; Problem with allocation - let it know we did not initialize
          *sbClass\InitDone = #False
          *sbClass\MemSize = 0
       EndIf
       ;
       ProcedureReturn *sbClass
       ; Return the pointer to our new stringbuilder class.
    EndProcedure

    Procedure sbClear(*inSBClass.StringBuilder)
       ;
       If *inSBClass\InitDone
          FreeMemory(*inSBClass\pString)
          *inSBClass\pString = 0
          *inSBClass\StringSize = 0
          *inSBClass\BlockSize = 0
          *inSBClass\InitDone = #False
          *inSBClass\MemSize = 0
       EndIf
       ;
    EndProcedure

    Procedure sbDestroy(*inSBClass.StringBuilder)
       ;
       If *inSBClass\InitDone
          FreeMemory(*inSBClass\pString)
          *inSBClass\pString = 0
          *inSBClass\StringSize = 0
          *inSBClass\BlockSize = 0
          *inSBClass\InitDone = #False
          *inSBClass\MemSize = 0
       EndIf
       ;
       FreeMemory(*inSBClass)
       ;
    EndProcedure

    Procedure sbAdd(*inSBClass.StringBuilder, inString)
       ;
       Protected StrLen
       Protected pNewString
       Protected NewMemSize
       Protected NewStringSize
       ;
       StrLen = MemoryStringLength(inString)
       ;
       NewStringSize = StrLen + *inSBClass\StringSize
       ;
       If NewStringSize + 1 > *inSBClass\MemSize
          NewMemSize = *inSBClass\MemSize + *inSBClass\BlockSize
          pNewString=AllocateMemory(NewMemSize)
          If pNewString = 0 : ProcedureReturn #False : EndIf
          CopyMemory(*inSBClass\pString, pNewString, *inSBClass\StringSize)
          FreeMemory(*inSBClass\pString)
          *inSBClass\pString = pNewString
          *inSBClass\MemSize = NewMemSize
       EndIf
       ;
       CopyMemory(inString, *inSBClass\pString + *inSBClass\StringSize, StrLen)
       *inSBClass\StringSize = NewStringSize
       ;
    EndProcedure

    Procedure sbAddLiteral(*inSBClass.StringBuilder, inString.s)
       ;
       Protected sAddress
       Protected StrLen
       Protected pNewString
       Protected NewMemSize
       Protected NewStringSize
       ;
       sAddress = @inString
       StrLen = MemoryStringLength(sAddress)
       ;
       NewStringSize = StrLen + *inSBClass\StringSize
       ;
       If NewStringSize + 1 > *inSBClass\MemSize
          NewMemSize = *inSBClass\MemSize + *inSBClass\BlockSize
          pNewString=AllocateMemory(NewMemSize)
          If pNewString = 0 : ProcedureReturn #False : EndIf
          CopyMemory(*inSBClass\pString, pNewString, *inSBClass\StringSize)
          FreeMemory(*inSBClass\pString)
          *inSBClass\pString = pNewString
          *inSBClass\MemSize = NewMemSize
       EndIf
       ;
       CopyMemory(sAddress, *inSBClass\pString + *inSBClass\StringSize, StrLen)
       *inSBClass\StringSize = NewStringSize
       ;
    EndProcedure

    Procedure.s sbGetString(*inSBClass.StringBuilder)
       ;
       Protected WholeString.s
       ;
       WholeString = PeekS(*inSBClass\pString)
       ;
       ProcedureReturn WholeString
       ;
    EndProcedure

    Procedure.s sbGetStringAndDestroy(*inSBClass.StringBuilder)
       ;
       Protected WholeString.s
       ;
       WholeString = PeekS(*inSBClass\pString)
       sbDestroy(*inSBClass)
       ;
       ProcedureReturn WholeString
       ;
    EndProcedure

    Procedure sbLength(*inSBClass.StringBuilder)
      Protected iLength
      iLength = *inSBClass\StringSize
      ProcedureReturn iLength
    EndProcedure


    sHold.s = "1"
    sDelimiter.s = ","
    q = sbCreate(2048)
    ; Create a new string builder with a blocksize of 2048.  You can create as many as you like.  Just use a new long variable and pass it as a reference to the various functions.
    sbAdd(q, @sHold)
    ; Add a string by the address of the string variable
    sbAdd(q, @sDelimiter)
    sbAddLiteral(q, "Hi!")
    ; Add by string rather than string address.
    Debug sbGetString(q)
    ; Return the string itself
    Debug "Length: "+Str(sbLength(q))
    ; Return the character count - 1 based. So "Hi" will return 2.  Our example here should return 5.
    sbDestroy(q)
    ; Destroy the string builder and clean up the used memory.
So you should be able to create and use as many string builders as you want. It'd be great if anyone could spot any way to update the code and make it faster or better. In fact, I'd appreciate if people could give it a shot. I'm just starting with pointers memory functions so I'm sure I messed something up.

And thanks to helpy and froggerprogger for the code and examples :D

[Edit: I snuck in a "sbGetStringAndDestroy" procedure that will return the string and destroy the string builder. I'm not sure others will find this useful but I often need to return the built string as a procedurereturn so it's useful for me to return the string without setting the string builder to a string, destroying it and then returning the string]