Code archives/Miscellaneous/TStringStream
This code has been declared by its author to be Public Domain code.
Download source code
| |||||
What if you had a function that takes a stream as an argument but you wanted to pass a string to it instead? The way I see it, you'd have four options. The first would be to give up and walk away, and that's no option at all. The second would be to rewrite the function in question to use strings instead, but isn't that just a terrible lot of work? The third would be to hack together a stream with the string's contents copied into it, but that's inefficient and totally unacceptable. That's where the fourth option comes in. TStringStream allows you to create a TStream which reads and writes data directly to and from a string. Note that the writing of data is actually kind of hackish and while it's perfectly safe and functional on my system using BlitzMax 1.50, there's no telling how the structure of the BBString struct has or is going to change, or changes depending on platform. Make sure the example runs without a hitch (which includes a test before it attempts to write any data) before using the writing with your particular compiler. Also, note that data is not allowed to be written past EOF as that would involve memory reallocation. Note that writing to a string stream goes against what BlitzMax believes should happen when you want to alter the contents of a string, so be aware of how writing to a string stream may affect your program. A good way to get aware is to run and understand the included example code. | |||||
' --+-----------------------------------------------------------------------------------------+-- ' | This code was originally written by Sophie Kirschner (sophiek@pineapplemachine.com) | ' | It is released as public domain. Please don't interpret that as liberty to claim credit | ' | that isn't yours, or to sell this code when it could otherwise be obtained for free | ' | because that would be a really shitty thing of you to do. | ' --+-----------------------------------------------------------------------------------------+-- SuperStrict Import brl.stream Rem ' Example code Import brl.standardio Local data$= "Solving the following riddle will reveal the awful secret behind the universe, "+ .. "assuming you do not go utterly mad in the attempt. If you already happen to know "+.. "the awful secret behind the universe, feel free to skip ahead." ' Read from the data variable as though it were a stream Print "~nResult of opening a string stream with data string and applying ReadLine():~n" Local stream:TStringStream=TStringStream.Create(data) Print ReadLine(stream) If TestTStringStream() ' Write over a few bytes Print "~nNow overwriting the first several bytes of the string stream..." SeekStream(stream,0) For Local i%=0 Until 16 WriteByte stream,Asc("A")+i Next ' Print the string as it now exists Print "~nThis is what the string looks like after writing some stuff to it:~n" Print data ' Demonstrate how bugs can be made if you're not careful with which strings you're writing to Print "~nNow testing buggy behavior of writing to the stream." data="Hello World" stream.open(data) Print "This is what the new string looks like before tampering with it: ~q"+data+"~q" WriteByte(stream,Asc("Y")) Print "This is what the string looks like after tampering with it: ~q"+data+"~q" Local hiworld$="Hello World" Print "This is what the string literal ~qHello World~q now points to when assigned to another variable: "+hiworld hiworld="Hello "+"World" Print "This is what the string literal ~qHello ~q + ~qWorld~q now points to when assigned to another variable: "+hiworld Else Print "~nWhat do you know? Writing data isn't going to work out-of-the-box. Check the ~qabout~q bit of the Write method to see about fixing it for your particular compiler." EndIf EndRem ' Here's a handy function that will return true if the TStringStream Write method is going to function like it's supposed to. ' Contents of teststring doesn't really matter, just maybe use 8 or so characters at the very least. ' Returns true if everything looks good, false otherwise. Function TestTStringStream%(teststring$="This is a test") Local dataptr@@ Ptr=Short Ptr(Int Ptr(Varptr teststring)[0])+6 For Local i%=0 Until teststring.length If dataptr[i]<>teststring[i] Return False Next Return True End Function Rem bbdoc: String stream type about: Useful if you'd like to use the same code for reading data from a string as reading from another stream, such as a file stream. This class does not support writing past EOF. End Rem Type TStringStream Extends TStream Field source$,position% Rem bbdoc: Create a stream based on a #String object End Rem Function Create:TStringStream(source$) Local stream:TStringStream=New TStringStream stream.source=source stream.position=0 Return stream End Function Rem bbdoc: Have the stream read a different #String object End Rem Method Open(openstring$) Flush source=openstring End Method Rem bbdoc: Get stream end of file status returns: True for end of file reached, otherwise False End Rem Method Eof%() Return position>=source.length End Method Rem bbdoc: Get position of seekable stream returns: Stream position as a byte offset End Rem Method Pos%() Return position End Method Rem bbdoc: Get size of seekable stream returns: Size, in bytes, of seekable stream End Rem Method Size%() Return source.length End Method Rem bbdoc: Seek to position in seekable stream returns: New stream position End Rem Method Seek%(pos%) position=pos Return position End Method Rem bbdoc: Flush stream about: Flushes any internal stream buffers. End Rem Method Flush() source=Null position=0 End Method Rem bbdoc: Close stream about: Closes the stream after flushing any internal stream buffers. End Rem Method Close() Flush End Method Rem bbdoc: Read at least 1 byte from a stream returns: Number of bytes successfully read about: If this method returns 0, the stream has reached end of file. End Rem Method Read%(buf@ Ptr,count%) If position+count>source.length count=source.length-position If count<=0 Return 0 EndIf For Local i%=0 Until count buf[i]=source[position] position:+1 Next Return count End Method Rem bbdoc: Write at least 1 byte to a stream returns: Number of bytes successfully written about: If this method returns 0, the stream has reached end of file. Note that this method depends on the structure of the BBString data type (see mod\brl.mod\blitz.mod\blitz_string.h) being the same across all platforms and versions. Which probably isn't going to happen. Chances are you'll have to tweak this method to cooperate with your particular BlitzMax compiler. For the record, I wrote this particular implementation for Blitzmax 1.50 on Windows 8. If you're having trouble I recommend simply checking the data located at the pointer pointed at by a string's pointer (yo dawg) and finding where the characters start in relation to it. Also beware the odd behavior that can result from overwriting the contents of a BBString. Writing over a string doesn't just change that string, it essentially causes future strings that would point to the same unaltered literals to point to the altered data instead. (Since technically they're one and the same after writing over stuff.) End Rem Method Write%(buf@ Ptr,count%) If position+count>source.length count=source.length-position If count<=0 Return 0 EndIf ' This next line might warrant an explanation. Here it is broken down into its constituent parts with each step explained: ' Local dataptr@@ Ptr= This part's simple. The location of the source string's data belongs in this variable as a pointer to a short. ' Short Ptr( ) Convert the integer about to be grabbed from the String to the location of its corresponding BBString struct. ' Int Ptr(Varptr source)[0] Actually grab the pointer to that struct, which defined by the integer at the location of the string's pointer ' +6 This just happens to be the amount of string-related data preceding the struct's array of BBChars. ' +position Finally, just add the position in the string stream to the location of the pointer. Local dataptr@@ Ptr=Short Ptr(Int Ptr(Varptr source)[0])+6+position For Local i%=0 Until count dataptr[i]=buf[i] position:+1 Next Return count End Method End Type |
Comments
| ||
I'm pretty sure strings are meant to be immutable. I'm not sure it's such a good idea to be writing over their content in this way. |
| ||
They are, yeah. It might be unconventional, but the behavior should be predictable. I screwed with it a bit and found only one caveat. If you write over some string data, then future pointers to the same string will think it's the string you wrote over instead. Therefore: not generally a good idea to write over oft-reused strings, probably harmless to write over strings you know you're only going to use once. I edited the description and code example with a demonstration of the weird behavior that arises when you write to a string this way. |
Code Archives Forum