Page 1 of 1

Stringbuilder

Posted: Fri Jan 24, 2014 5:39 pm
by Fenix
Hi,

I'm trying to write my own stringbuilder routine but it won't work as it should. The resulting string differs from the input (I guess while copying it with CopyMemory to the string buffer). Still, I can't figure out why...

Here's the code:

Code: Select all

Structure _structStringManagement
  iBlockSize.i        ; minimum bytes we allocate if we need more space to add a string to the buffer
  iBufferSize.i       ; total memory size we've allocated
  iPositionInBuffer.i ; actual position in the buffer
  iBufferAddress.i    ; address of the buffer (aka allocated memory)
  iInitDone.i         ; true if the string builder has been initilized (memory was reserved)
EndStructure

Procedure _sbInit(iBlocksize.i)
  
  Global _StringBuffer._structStringManagement
  
  With _StringBuffer
    \iBlockSize   = 1024
    \iBufferAddress = AllocateMemory(iBlocksize)
    If \iBufferAddress
      \iInitDone = #True
    EndIf
  EndWith
  
EndProcedure

Procedure.i _sbAddString(sString.s)
  ; --- adds a new string to the buffer
  
  Protected iStringAddress.i
  Protected iStringLength.i
  Protected iNewStringSize.i
  Protected iNewStringBufferAddress.i
  
  iStringAddress = @sString
  iStringLength = MemoryStringLength(iStringAddress, #PB_Unicode)
  
  With _StringBuffer
    iNewStringSize = iStringLength + \iPositionInBuffer

   If iNewStringSize + 1 > \iBlockSize
      
      iNewStringBufferAddress = AllocateMemory(\iBufferSize + \iBlockSize)
      If iNewStringSize 
        CopyMemory(\iBufferAddress, iNewStringBufferAddress, \iPositionInBuffer)
        FreeMemory(\iBufferAddress)
        \iBufferAddress = iNewStringBufferAddress
        \iBufferSize    = \iBufferSize + \iBlockSize
      Else
        ProcedureReturn #False
      EndIf
    EndIf
    
    CopyMemory(iStringAddress, \iBufferAddress + \iPositionInBuffer, iStringLength * 2)
        
    \iPositionInBuffer + iStringLength
    
    Debug "string length  = " + iStringLength
    Debug "string4buffer  = " + PeekS(iStringAddress)
    Debug "buffer (ascii) = " + PeekS(\iBufferAddress, -1, #PB_Ascii)
    Debug "buffer (uni)   = " + PeekS(\iBufferAddress, -1, #PB_Unicode)
    Debug "buffer (utf-8) = " + PeekS(\iBufferAddress, -1, #PB_UTF8)
    Debug "buffer (def)   = " + PeekS(\iBufferAddress)
    Debug "bufferpos      = " + \iPositionInBuffer
    Debug ""
  EndWith
  
EndProcedure

_sbInit(4096)

For iCounter = 1 To 100
  _sbAddString(Str(iCounter))
  _sbAddString(" , ")
  _sbAddString(" 123 #'+~")
Next

Debug _sbGetString()
Any idea, where the problem hides??

Greetz,
Fenix

Re: Stringbuilder

Posted: Fri Jan 24, 2014 8:19 pm
by Demivec
Fenix wrote:Any idea, where the problem hides??
There seems to be a few areas of concern. But first, is this supposed to be compiled in ASCII or Unicode? I'm guessing it's Unicode.

Second, there is no '_sbGetString()' procedure defined so it is not quite clear what is different in the output compared to the input.


Assuming it is to be compiled in unicode, here is a list of the errors I found:

Code: Select all

;line 35, iPositionInBuffer is in bytes, iStringLength is in characters (2 bytes for unicode)
    iNewStringSize = iStringLength + \iPositionInBuffer 

;line 37 ;iNewStringSize would actually be the filled buffersize in bytes, after iBlockSize is reached this will always trigger
    If iNewStringSize + 1 > \iBlockSize
  
;line 39, iNewStringSize may be larger by more than one iBlockSize (use \iNewStringSize instead of \BufferSize), use ReAllocateMemory()
      iNewStringBufferAddress = AllocateMemory(\iBufferSize + \iBlockSize)

;line 40, this should be testing iNewStringBufferAddress
      If iNewStringSize

;line 52, iPositionInBuffer is in bytes, iStringLength is in characters (2 bytes for unicode)
    \iPositionInBuffer + iStringLength
The code with corrections made:

Code: Select all

Structure _structStringManagement
  iBlockSize.i        ; minimum bytes we allocate if we need more space to add a string to the buffer
  iBufferSize.i       ; total memory size we've allocated
  iPositionInBuffer.i ; actual position in the buffer
  iBufferAddress.i    ; address of the buffer (aka allocated memory)
  iInitDone.i         ; true if the string builder has been initilized (memory was reserved)
EndStructure

Procedure _sbInit(iBlocksize.i)
  
  Global _StringBuffer._structStringManagement
  
  With _StringBuffer
    \iBlockSize   = 1024
    \iBufferAddress = AllocateMemory(iBlocksize)
    If \iBufferAddress
      \iInitDone = #True
    EndIf
  EndWith
  
EndProcedure

Procedure.i _sbAddString(sString.s)
  ; --- adds a new string to the buffer
  
  Protected iStringAddress.i
  Protected iStringLength.i
  Protected iNewStringSize.i
  Protected iNewStringBufferAddress.i
  
  iStringAddress = @sString
  iStringLength = MemoryStringLength(iStringAddress, #PB_Unicode)
  
  With _StringBuffer
    iNewStringSize = iStringLength * 2 + \iPositionInBuffer

    If iNewStringSize + 1 - \iBufferSize > \iBlockSize
      
      iNewStringBufferAddress = ReAllocateMemory(\iBufferAddress, iNewStringSize + \iBlockSize)
      If iNewStringBufferAddress
        ;CopyMemory(\iBufferAddress, iNewStringBufferAddress, \iPositionInBuffer)  ;already performed by ReAllocateMemory()
        ;FreeMemory(\iBufferAddress)                                               ;already performed by ReAllocateMemory()
        \iBufferAddress = iNewStringBufferAddress
        \iBufferSize    = MemorySize(\iBufferAddress)
      Else
        ProcedureReturn #False
      EndIf
    EndIf
    
    CopyMemory(iStringAddress, \iBufferAddress + \iPositionInBuffer, iStringLength * 2)
        
    \iPositionInBuffer + iStringLength * 2
    
    Debug "string length  = " + iStringLength
    Debug "string4buffer  = " + PeekS(iStringAddress)
    ;Debug "buffer (ascii) = " + PeekS(\iBufferAddress, -1, #PB_Ascii)    ;data is in unicode not ASCII
    Debug "buffer (uni)   = " + PeekS(\iBufferAddress, -1, #PB_Unicode)
    ;Debug "buffer (utf-8) = " + PeekS(\iBufferAddress, -1, #PB_UTF8)     ;data is in unicode not UTF8
    Debug "buffer (def)   = " + PeekS(\iBufferAddress)
    Debug "bufferpos      = " + \iPositionInBuffer
    Debug ""
  EndWith
  
EndProcedure

_sbInit(4096)

For iCounter = 1 To 100
  _sbAddString(Str(iCounter))
  _sbAddString(" , ")
  _sbAddString(" 123 #'+~")
Next

;Debug _sbGetString()    ;undefined in code

Re: Stringbuilder

Posted: Fri Jan 24, 2014 9:46 pm
by Fenix
Demivec wrote:
Fenix wrote:Any idea, where the problem hides??
There seems to be a few areas of concern. But first, is this supposed to be compiled in ASCII or Unicode? I'm guessing it's Unicode.
Indeed it's compiled in unicode mode. I forgot to mention that.
Demivec wrote:Second, there is no '_sbGetString()' procedure defined so it is not quite clear what is different in the output compared to the input.
Forgot to copy that procedure. It's just

Code: Select all

ProcedureReturn PeekS(_StringBuffer\iBufferAddress)
I totally got the MemoryStringLength() wrong. Thought it would return the total number of bytes, so for unicode twice the character count :oops: Didn't see that, guess I was staring at the code for too long :lol:

Line 39: You're right, a string could be larger than 1024 bytes. Didn't implement a checking here as I know the strings I'm using in the actual program are limited in size and far less then 1kb. But I keep that in mind for other projects.

Many thanks to you!!

Greetz,
Fenix

Re: Stringbuilder

Posted: Sat Jan 25, 2014 1:51 am
by IdeasVacuum
Consider using StringByteLength(), it's much easier.