There's nothing wrong with Concatenate. If you read the comments at the beginning of the routine, it says that the first string value must have space available following the end of string zero byte to hold the value of the second string. If you have not left enough space, that's an overrun or buffer overrun if you consider the first string value as being in a buffer. If you call Concatenate multiple times, there must always be enough room in the first string value's memory area to hold the resulting string.
The Combine method uses a buffer internal to the Strings object to hold the result value as it's assembled and it tests the resulting string size to make sure it will fit in the buffer. You have to be careful about using the result string in the Strings object because the buffer is used again whenever you call one of the methods in the object that produces a string result. You're limited in how big a string you can produce by the size of the buffer (128 bytes by default). The String2 object has no such limit.
Okay now I understand completely. The reason why combine works perfectly is because the buffer size of STR_MAX_LENGTH is already initialized. Well gee I look like an idiot. It isn't the first time and I am sure it won't be the last
There nothing about whether you look like a genius or an idiot. Part of being a good programmer is reading the documentation and taking time to understand your tools. It's also helpful to "execute" your program on paper, write down what's passed to a method call or operator and write down the result as well (and maybe compare that to what you're actually getting). The "trick" is that we often have an idea of what results a routine is supposed to produce, particularly when we've written it. Sometimes the "bug" is that the routine doesn't in fact produce the value we expect or maybe it misbehaves when some boundary condition holds true that that's the time it produces an anomalous result. Really good debuggers have developed an intuitive sense of where these boundary conditions might be and sometimes check those situations first.
I suppose these kind of instructions
CurrentDateResult1 := STRING("Monday ")
don't do what you expect. They DO NOT copy the content of STRING into the buffer. STRING only reserves some memory and puts the string there. The return value of STRING is the address where the string Monday has been stored.
Try to initialize with (after setting back CurrentDateResult1 to a byte array)
bytemove( @CurrentDateResult1, string( "Monday" ), 6 )
If you need one string more often (for readonly access) it is better to define it in a dat section ONCE, as multiple usages of STRING() will create multiple copies of the string in memory even if it's the same.
Spin, like C, uses the address of a block of storage to represent a string. STRING( ... ) produces an address which, on the Prop 1, takes 16 bits and must be stored in a WORD or LONG. Both Concatenate and Combine require their string parameters to be addresses, so you'd need:
Var Byte string1value[128], string2value[128]
PUB GetCurrentDate1 : string1pointer
' string1pointer is the return value of the method, result is the default name,
' but we're overriding that here as an example. It's always a Long.
string1pointer := @string1value ' Set pointer to start of string
string1value[0] := 0 ' Set string value to null string
string1pointer := Combine(string1pointer, STRING("Monday "))
string1pointer := Combine(string1pointer, string("1"))
string1pointer := Combine(string1pointer, string("/"))
string1pointer := Combine(string1pointer, string("17"))
string1pointer := Combine(string1pointer, string("/"))
string1pointer := Combine(string1pointer, string("11"))
RETURN string1pointer
' We don't actually need a Return here because it's assumed to come at the end of the method
PUB GetCurrentDate2 ' This is a cleaner neater version using Concatenate
result := @string2value ' Set pointer to start of string
string2value~ ' Clear first byte of string to make null string
Concatenate(result, string("Monday ")) ' We don't need to save the value of Concatenate
Concatenate(result, string("1")) ' because it's always the same as the first parameter.
Concatenate(result, string("/"))
Concatenate(result, string("17"))
Concatenate(result, string("/"))
Concatenate(result, string("11"))
' default "return result" is here
Not really! With this you access some memory which is not your buffer, it's the memory that's been reserved by the STRING instruction which is only big enough to hold that string ("Monday" in this case). So, depending of what you append you will overwrite memory containing other data.
Have a closer look at the combine function. The expected parameters are addresses, but you call it with variable content as first parameter.
These calls should then look like
Concatenate(@CurrentDateResult1, string("1"))
With the first change I mentioned and this one, you really work IN the buffer.
This is the same lesson that you should already know from C++ pointers. Don't call functions which expect a pointer with a variable but with the pointer to the variable.
You have mentioned that you are a C/C++ programmer, so I'll make an analogy with C. Your Combine method is similar to strcat in C. Translating your code to C it would look like:
That is very interesting. I am glad you showed two examples. I have not tested to see if both examples work in both methods, however, it appears to be the exact same thing, just written differently.
string1pointer := @string1value ' Set pointer to start of string
string1value[0] := 0 ' Set string value to null string
result := @string2value ' Set pointer to start of string
string2value~ ' Clear first byte of string to make null string
MagIO2
For many years I just hacked my way through it using routines that gave me the desired results. I have no formal education pertaining to programming, whatever I learned, I learned on my own, and did become pretty decent at it. However, pointers have always been my biggest problem pertaining to programming. Before I slowed down with C++, I had a pretty good grasp of using pointers, but that was several years ago. Since then, I have only dabbled in it now and then, for particular projects.
Anyhow guys, thanks alot, I appreciate the "lesson"
Believe me, pointers are anybodys problem in C/C++ at least at the beginning. That's why they tried to get rid of these in later programming languages.
If you have a look at Mikes code (post #9) you find a good tip how to handle this, it's called naming convention. If you have variables that are meant to keep pointers to memory locations, then add ptr or pointer to the name. The same applies to the names of parameters.
This way you see on a first glance what this variable contains or this parameter expects. If you call a function you can easily decide how to call it:
If the parameter contains ptr you'd either call it by using @variable or by variablePtr (of course this then should contain a valid address).
For return values in most cases you have to know what these will return.
I suppose these kind of instructions
CurrentDateResult1 := STRING("Monday ")
if you need one string more often (for readonly access) it is better to define it in a dat section ONCE, as multiple usages of STRING() will create multiple copies of the string in memory even if it's the same.
So, if I understand you correctly, using DAT would be equivelant to using 'Static' in say (Java) and VAR would be equivelant to a private CLASS variable?
All data in a DAT section is only there once. Even if you have more than one instance of the object. In opposite each object has it's own independent VAR section.
In my post I talked about the STRING(). If you use STRING( "Test" ) in ten different places of your code (same object or different object does not matter), you really have the string "Test" ten times in the memory image of this program.
I have found the whole buffer issue too confusing, so I've been coding some new structures for strings. This is a hybridisation of Kye's string code, Pointers in C, my knowledge of the way strings are handled internally in Basic, an example of how strings are handled in the Pascal like version of Basic called Sbasic, and my desire to make strings *easy* to use. My rules of thumb:
1) Try not to have any internal buffers in the string handling code. All buffer space must be declared in the "main" program.
2) Allocate enough space. Sbasic defaulted to 80 bytes.
3) Make strings global to save space. But try to code them locally within subroutines as this is good programming practice.
4) @ is the equivalent of a pointer in C. Use @ liberally in code.
So, declare some string space at the beginning of the program. You can call these whatever you like. I've only need three generic strings so far as they are being recycled but in large assembly programs I found 8 was sufficient.
VAR
byte LineOfText1[80] ' general purpose string buffer
byte LineOfText2[80] ' general purpose string buffer
byte LineOfText3[80] ' general purpose string buffer
This concatenate is from Kye's string handling code and I would very much hope this does not overrun (unless you have a string > 80 bytes!)
PUB stringConcatenate(whereToPut, whereToGet) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Concatenates a string onto the end of another. This method can corrupt memory.
'' //
'' // Returns a pointer to the new string.
'' //
'' // WhereToPut - Address of the string to concatenate a string to.
'' // WhereToGet - Address of where to get the string to concatenate.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bytemove((whereToPut + strsize(whereToPut)), whereToGet, (strsize(whereToGet) + 1))
return whereToPut
PUB stringCopy(whereToPut, whereToGet) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Copies a string from one location to another. This method can corrupt memory.
'' //
'' // Returns a pointer to the new string.
'' //
'' // WhereToPut - Address of where to put the copied string.
'' // WhereToGet - Address of where to get the string to copy.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bytemove(whereToPut, whereToGet, (strsize(whereToGet) + 1))
return whereToPut
eg
str.stringcopy(@lineoftext1,string("Hello World")) ' generally the source would be a fixed string
There are a whole lot more string functions in Kye's code. But not the ones I use the most, ie Left and Mid. So I coded them.
And in doing so I discovered a bit of a programming problem that might explain why it has been hard to find these routines. In general terms, a common line of code might be:
returnvariable := function(passedvariable)
and that is fine except when the function needs to know returnvariable as well. So to solve this, pass returnvariable and passvariable to the function and use it like this, ie with no returnvariable :=
str.mid(@lineoftext1,@lineoftext2,4,2) ' find middle characters in a string
These are a series of test routines to use these string functions. Note that @ gets used a lot.
'printstring(@lineoftext1) ' testing string functions
'str.left(@lineoftext1,@lineoftext2,3) ' find leftmost characters in a string
'printstring(@lineoftext2)
'str.trimstring(@lineoftext1)
'printstring(@lineoftext1)
'str.trimstring(@lineoftext1)
'str.mid(@lineoftext1,@lineoftext2,4,2) ' find middle characters in a string
'printstring(@lineoftext2)
'printstring(str.integertodecimal(str.len(@lineoftext2),5))
'str.copy(string("hello"),@lineoftext1) ' copy fixed value into a string
'printstring(@lineoftext1)
'str.str(@lineoftext1,55) ' decimal to string with no leading zeros
'printstring(@lineoftext1)
'str.stringfill(@lineoftext1,10,65)
'printstring(@lineoftext1)
'str.str(@lineoftext1,1234)' convert to string
'str.str(@lineoftext1,str.decimaltointeger(@lineoftext1)) ' convert back
'printstring(@lineoftext1)
' str.left(@lineoftext1,@lineoftext2,str.len(@lineoftext1)-4) ' remove extension
' printstring(@lineoftext2) ' print it out
and the point of all this?
Well, the aim is not to have any internal buffer overruns in functions that you call. I find those sorts of bugs hard to find. So there is no internal buffer. The code seems to end up simpler this way:
PUB Mid(Source,Destination,Start,Number) ' returns strings starting at start with number characters
Start-- ' so starts at the right position
Source += Start ' position 1 is the first character
repeat Number
byte[Destination] := byte[Source]
Source++
Destination++
byte[Destination] :=0 'add in the zero terminator
Kye's code below, with some additions near the end from myself:
{{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// ASCII0 String Engine
//
// Author: Kwabena W. Agyeman
// Updated: 8/28/2010
// Designed For: P8X32A
// Version: 1.2
//
// Copyright (c) 2010 Kwabena W. Agyeman
// See end of file for terms of use.
//
// Update History:
//
// v1.0 - Original release - 4/10/2009.
// v1.1 - Made code faster - 8/18/2009.
// v1.2 - Updated library functions, fixed bugs, and made code more robust against whitespace and capitalization - 7/27/2010.
//
// For each included copy of this object only one spin interpreter should access it at a time.
//
// Nyamekye,
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}}
VAR
word tokenStringPointer
byte decimalString[12], hexadecimalString[9], binaryString[33], characterToStringPointer, characterToString[255]
PUB buildString(character) '' 4 Stack longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Builds a string from individual characters. Use "builtString" to get the address of the string.
'' //
'' // If the backspace character is put into the string it is automatically evaluated by removing the previous character.
'' //
'' // If 254 characters are put into the string all characters excluding backspace that are put into the string are ignored.
'' //
'' // Character - The next character to include in the string. Null will be ignored.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
ifnot(characterToStringPointer)
bytefill(@characterToString, 0, 255)
if(characterToStringPointer and (character == 8))
characterToString[--characterToStringPointer] := 0
elseif(character and (characterToStringPointer <> 254))
characterToString[characterToStringPointer++] := character
PUB builtString(resetString) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the pointer to the string built from individual characters.
'' //
'' // Reset - If true the next call to "buildString" will begin building a new string and the old string will be destroyed.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
characterToStringPointer &= not(resetString)
return @characterToString
PUB builderNumber '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns the number of characters in the string builder buffer.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return characterToStringPointer
PUB builderFull '' 3 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Returns true if the string builder buffer is full and false if not.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
return (characterToStringPointer == 254)
PUB stringCompareCS(characters, otherCharacters) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Compares two strings case sensitively.
'' //
'' // Returns zero if the two strings are equal.
'' // Returns a positive value if "characters" comes lexicographically after "otherCharacters".
'' // Returns a negative value if "characters" comes lexicographically before "otherCharacters".
'' //
'' // Characters - A pointer to a string of characters.
'' // OtherCharacters - A pointer to another string of characters.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat
result := (byte[characters] - byte[otherCharacters++])
while(byte[characters++] and (not(result)))
PUB stringCompareCI(characters, otherCharacters) '' 9 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Compares two strings case insensitively.
'' //
'' // Returns zero if the two strings are equal.
'' // Returns a positive value if "characters" comes lexicographically after "otherCharacters".
'' // Returns a negative value if "characters" comes lexicographically before "otherCharacters".
'' //
'' // Characters - A pointer to a string of characters.
'' // OtherCharacters - A pointer to another string of characters.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat
result := (ignoreCase(byte[characters]) - ignoreCase(byte[otherCharacters++]))
while(byte[characters++] and (not(result)))
PUB stringCopy(whereToPut, whereToGet) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Copies a string from one location to another. This method can corrupt memory.
'' //
'' // Returns a pointer to the new string.
'' //
'' // WhereToPut - Address of where to put the copied string.
'' // WhereToGet - Address of where to get the string to copy.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bytemove(whereToPut, whereToGet, (strsize(whereToGet) + 1))
return whereToPut
PUB stringConcatenate(whereToPut, whereToGet) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Concatenates a string onto the end of another. This method can corrupt memory.
'' //
'' // Returns a pointer to the new string.
'' //
'' // WhereToPut - Address of the string to concatenate a string to.
'' // WhereToGet - Address of where to get the string to concatenate.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
bytemove((whereToPut + strsize(whereToPut)), whereToGet, (strsize(whereToGet) + 1))
return whereToPut
PUB stringToLowerCase(characters) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Demotes all upper case characters in the set of ("A","Z") to their lower case equivalents.
'' //
'' // Characters - A pointer to a string of characters to convert to lowercase.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat strsize(characters--)
result := byte[++characters]
if((result => "A") and (result =< "Z"))
byte[characters] := (result + 32)
PUB stringToUpperCase(characters) '' 4 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Promotes all lower case characters in the set of ("a","z") to their upper case equivalents.
'' //
'' // Characters - A pointer to a string of characters to convert to uppercase.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat strsize(characters--)
result := byte[++characters]
if((result => "a") and (result =< "z"))
byte[characters] := (result - 32)
PUB trimString(characters) '' 8 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Removes white space and new lines arround the outside of string of characters.
'' //
'' // Returns a pointer to the trimmed string of characters.
'' //
'' // Characters - A pointer to a string of characters to be trimmed.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
result := ignoreSpace(characters)
characters := (result + ((strsize(result) - 1) #> 0))
repeat
case byte[characters]
8 .. 13, 32, 127: byte[characters--] := 0
other: quit
PUB tokenizeString(characters) '' 8 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Removes white space and new lines arround the inside of a string of characters.
'' //
'' // Returns a pointer to the tokenized string of characters, or an empty string when out of tokenized strings of characters.
'' //
'' // Characters - A pointer to a string of characters to be tokenized, or null to continue tokenizing a string of characters.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
if(characters)
tokenStringPointer := characters
result := tokenStringPointer := ignoreSpace(tokenStringPointer)
repeat while(byte[tokenStringPointer])
case byte[tokenStringPointer++]
8 .. 13, 32, 127:
byte[tokenStringPointer - 1] := 0
quit
PUB findCharacter(stringToSearch, characterToFind) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Searches a string of characters for the first occurence of the specified character.
'' //
'' // Returns the address of that character if found and zero if not found.
'' //
'' // StringToSearch - A pointer to the string of characters to search.
'' // CharacterToFind - The character to find in the string of characters to search.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat strsize(stringToSearch--)
if(byte[++stringToSearch] == characterToFind)
return stringToSearch
PUB replaceCharacter(stringToSearch, characterToReplace, characterToReplaceWith) '' 11 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Replaces the first occurence of the specified character in a string of characters with another character.
'' //
'' // Returns the address of the next character after the character replaced on success and zero on failure.
'' //
'' // StringToSearch - A pointer to the string of characters to search.
'' // CharacterToReplace - The character to find in the string of characters to search.
'' // CharacterToReplaceWith - The character to replace the character found in the string of characters to search.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
result := findCharacter(stringToSearch, characterToReplace)
if(result)
byte[result++] := characterToReplaceWith
PUB replaceAllCharacters(stringToSearch, characterToReplace, characterToReplaceWith) '' 17 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Replaces all occurences of the specified character in a string of characters with another character.
'' //
'' // StringToSearch - A pointer to the string of characters to search.
'' // CharacterToReplace - The character to find in the string of characters to search.
'' // CharacterToReplaceWith - The character to replace the character found in the string of characters to search.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat while(stringToSearch)
stringToSearch := replaceCharacter(stringToSearch, characterToReplace, characterToReplaceWith)
PUB findString(stringToSearch, stringToFind) | index, size '' 7 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Searches a string of characters for the first occurence of the specified string of characters.
'' //
'' // Returns the address of that string of characters if found and zero if not found.
'' //
'' // StringToSearch - A pointer to the string of characters to search.
'' // StringToFind - A pointer to the string of characters to find in the string of characters to search.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
size := strsize(stringToFind)
if(size--)
repeat strsize(stringToSearch--)
if(byte[++stringToSearch] == byte[stringToFind])
repeat index from 0 to size
if(byte[stringToSearch][index] <> byte[stringToFind][index])
result := true
quit
ifnot(result~)
return stringToSearch
PUB replaceString(stringToSearch, stringToReplace, stringToReplaceWith) '' 13 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Replaces the first occurence of the specified string of characters in a string of characters with another string of
'' // characters. Will not enlarge or shrink a string of characters.
'' //
'' // Returns the address of the next character after the string of characters replaced on success and zero on failure.
'' //
'' // StringToSearch - A pointer to the string of characters to search.
'' // StringToReplace - A pointer to the string of characters to find in the string of characters to search.
'' // StringToReplaceWith - A pointer to the string of characters that will replace the string of characters found in the
'' // string of characters to search.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
result := findString(stringToSearch, stringToReplace)
if(result)
repeat (strsize(stringToReplaceWith) <# strsize(stringToReplace))
byte[result++] := byte[stringToReplaceWith++]
PUB replaceAllStrings(stringToSearch, stringToReplace, stringToReplaceWith) '' 19 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Replaces all occurences of the specified string of characters in a string of characters with another string of
'' // characters. Will not enlarge or shrink a string of characters.
'' //
'' // StringToSearch - A pointer to the string of characters to search.
'' // StringToReplace - A pointer to the string of characters to find in the string of characters to search.
'' // StringToReplaceWith - A pointer to the string of characters that will replace the string of characters found in the
'' // string of characters to search.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat while(stringToSearch)
stringToSearch := replaceString(stringToSearch, stringToReplace, stringToReplaceWith)
PUB integerToDecimal(number, length) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Converts an integer number to the decimal string of that number padded with zeros.
'' //
'' // Returns a pointer to the converted string.
'' //
'' // Number - A 32 bit signed integer number to be converted to a string.
'' // Length - The length of the converted string, "+" or "-" will be concatenated onto the head of converted string.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
length := (10 - ((length <# 10) #> 0))
decimalString := "+"
if(number < 0)
decimalString := "-"
if(number == negx)
bytemove(@decimalString, string("-2147483648KA"), 11)
else
repeat result from 10 to 1
decimalString[result] := ((||(number // 10)) + "0")
number /= 10
decimalString[length] := decimalString
return @decimalString[length]
PUB integerToHexadecimal(number, length) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Converts an integer number to the hexadecimal string of that number padded with zeros.
'' //
'' // Returns a pointer to the converted string.
'' //
'' // Number - A 32 bit signed integer number to be converted to a string.
'' // Length - The length of the converted string, negative numbers need a length of 8 for sign extension.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat result from 7 to 0
hexadecimalString[result] := lookupz((number & $F): "0".."9", "A".."F")
number >>= 4
return @hexadecimalString[8 - ((length <# 8) #> 0)]
PUB integerToBinary(number, length) '' 5 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Converts an integer number to the binary string of that number padded with zeros.
'' //
'' // Returns a pointer to the converted string.
'' //
'' // Number - A 32 bit signed integer number to be converted to a string.
'' // Length - The length of the converted string, negative numbers need a length of 32 for sign extension.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
repeat result from 31 to 0
binaryString[result] := ((number & 1) + "0")
number >>= 1
return @binaryString[32 - ((length <# 32) #> 0)]
PUB decimalToInteger(characters) | sign '' 10 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Converts a decimal string into an integer number. Expects a string with only "+-0123456789" characters.
'' //
'' // If the string has a "-" sign as its leading character the converted integer returned will be negated.
'' //
'' // If the string has a "+" sign as its leading character the converted integer returned will not be negated.
'' //
'' // Returns the converted integer. By default the number returned is positive and the "+" sign is unnecessary.
'' //
'' // Characters - A pointer to the decimal string to convert. The number returned will be 2's complement compatible.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
characters := checkSign(ignoreSpace(characters), @sign)
repeat (strsize(characters) <# 10)
ifnot(checkDigit(characters, "0", "9"))
quit
result := ((result * 10) + (byte[characters++] & $F))
result *= sign
PUB hexadecimalToInteger(characters) | sign '' 10 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Converts a hexadecimal string into an integer number. Expects a string with only "+-0123456789ABCDEFabdcef" characters.
'' //
'' // If the string has a "-" sign as its leading character the converted integer returned will be negated.
'' //
'' // If the string has a "+" sign as its leading character the converted integer returned will not be negated.
'' //
'' // Returns the converted integer. By default the number returned is positive and the "+" sign is unnecessary.
'' //
'' // Characters - A pointer to the hexadecimal string to convert. The number returned will be 2's complement compatible.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
characters := checkSign(ignoreSpace(characters), @sign)
repeat (strsize(characters) <# 8)
ifnot(checkDigit(characters, "0", "9"))
ifnot(checkDigit(characters, "A", "F") or checkDigit(characters, "a", "f"))
quit
result += $90_00_00_00
result := ((result <- 4) + (byte[characters++] & $F))
result *= sign
PUB binaryToInteger(characters) | sign '' 10 Stack Longs
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
'' // Converts a binary string into an integer number. Expects a string with only "+-01" characters.
'' //
'' // If the string has a "-" sign as its leading character the converted integer returned will be negated.
'' //
'' // If the string has a "+" sign as its leading character the converted integer returned will not be negated.
'' //
'' // Returns the converted integer. By default the number returned is positive and the "+" sign is unnecessary.
'' //
'' // Characters - A pointer to the binary string to convert. The number returned will be 2's complement compatible.
'' ////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
characters := checkSign(ignoreSpace(characters), @sign)
repeat (strsize(characters) <# 32)
ifnot(checkDigit(characters, "0", "1"))
quit
result := ((result << 1) + (byte[characters++] & 1))
result *= sign
PRI ignoreCase(character) ' 4 Stack Longs
result := character
if((character => "a") and (character =< "z"))
result -= 32
PRI ignoreSpace(characters) ' 4 Stack Longs
result := characters
repeat strsize(characters--)
case byte[++characters]
8 .. 13, 32, 127:
other: return characters
PRI checkSign(characters, signAddress) ' 5 Stack Longs
if(byte[characters] == "-")
result := -1
if(byte[characters] == "+")
result := 1
long[signAddress] := (result + ((not(result)) & 1))
return (characters + (||result))
PRI checkDigit(characters, low, high) ' 5 Stack Longs
result := byte[characters]
return ((low =< result) and (result =< high))
' added commands J Moxham Jan 2010
PUB endsWithString(stringToSearch, stringToFind) '' 12 Stack Longs
'' ┌──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┐
'' │ Checks if the string of characters ends with the specified characters. │
'' │ │
'' │ Returns true if yes and false if no. │
'' │ │
'' │ StringToSearch - A pointer to the string of characters to search. │
'' │ StringToFind - A pointer to the string of characters to find in the string of characters to search. │
'' └──────────────────────────────────────────────────────────────────────────────────────────────────────────────────────────┘
return ((stringToSearch + strsize(stringToSearch) - strsize(stringToFind)) == findString(stringToSearch, stringToFind))
PUB Left(Source, Destination,Number) ' returns the left number of characters
repeat Number
byte[Destination] := byte[Source]
Source++
Destination++
byte[Destination] :=0 ' add in the zero terminator
PUB Len(Source) ' returns the length of the string
return strsize(Source)
PUB Mid(Source,Destination,Start,Number) ' returns strings starting at start with number characters
Start-- ' so starts at the right position
Source += Start ' position 1 is the first character
repeat Number
byte[Destination] := byte[Source]
Source++
Destination++
byte[Destination] :=0 'add in the zero terminator
PUB Copy(Source,Destination) ' reverse order to stringcopy. Can also use to create strings eg copy(string("test"),@lineoftext1)
bytemove(Destination, Source, (strsize(Source) + 1))
PUB Str(Destination,Number) | n' convert a number to a string representation. Uses Basic syntax, ie leading character is a space if +ve, and is - if negative
n := number ' temp store for at the end when add the + or -
Destination += 10
byte[Destination] := 0 ' terminator
Destination--
repeat result from 10 to 1
byte[Destination] := ((||(number // 10)) + "0")
number /= 10
Destination--
destination++ ' points to start again
repeat while byte[destination] == "0" ' while equal to zero remove this leading zero
repeat result from 0 to 11 ' include the zero terminator as well
bytemove(destination+result,destination+result+1,1)' shuffle to left
repeat result from 11 to 1
bytemove(destination+result,destination+result-1,1) ' shuffle once to right - leading space or -
byte[destination] :=" " ' leading space if positive. Can use trim to remove this
if (n<0)
byte[destination] := "-"
PUB Stringfill(Destination, Number,AsciiValue)' fill string with a string, eg 3,65 is"AAA" and 2,32 is " "
bytefill(destination,AsciiValue,Number) ' same as Basic "string$" function and can also replicate space$
byte[destination+number] :=0 ' zero terminator
PUB Instr(Source,AsciiValue) | j ' find asciivalue in Source. Returns 0 if not found
j := 0
repeat result from 0 to strsize(Source)
if byte[source+result] == AsciiValue
j := result + 1 ' basic format where 1 is the last first character of a string
return j
'PUB Val(Source)' basic string to decimal value only works for integers not floating point, see DecimalToInteger above
{{
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// TERMS OF USE: MIT License
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
// Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation
// files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy,
// modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the
// Software is furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in all copies or substantial portions of the
// Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE
// WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
// COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
// ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
///////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////
}}
The CLIB object in the OBEX contains some of the standard C string functions, such as strcpy, strncpy, strcat, strcmp, strncmp, sscanf and sprintf. These are handy if you're famiiliar with the C functions.
Comments
The Combine method uses a buffer internal to the Strings object to hold the result value as it's assembled and it tests the resulting string size to make sure it will fit in the buffer. You have to be careful about using the result string in the Strings object because the buffer is used again whenever you call one of the methods in the object that produces a string result. You're limited in how big a string you can produce by the size of the buffer (128 bytes by default). The String2 object has no such limit.
Okay now I understand completely. The reason why combine works perfectly is because the buffer size of STR_MAX_LENGTH is already initialized. Well gee I look like an idiot. It isn't the first time and I am sure it won't be the last
Thanks Mike
Bruce
Mike please check this out. For the Combine only a Long works and for Concatenate neither Long nor Byte works properly. What am I doing wrong?
Bruce
CurrentDateResult1 := STRING("Monday ")
don't do what you expect. They DO NOT copy the content of STRING into the buffer. STRING only reserves some memory and puts the string there. The return value of STRING is the address where the string Monday has been stored.
Try to initialize with (after setting back CurrentDateResult1 to a byte array)
bytemove( @CurrentDateResult1, string( "Monday" ), 6 )
If you need one string more often (for readonly access) it is better to define it in a dat section ONCE, as multiple usages of STRING() will create multiple copies of the string in memory even if it's the same.
That is the only one that works.
Bruce
Have a closer look at the combine function. The expected parameters are addresses, but you call it with variable content as first parameter.
These calls should then look like
Concatenate(@CurrentDateResult1, string("1"))
With the first change I mentioned and this one, you really work IN the buffer.
This is the same lesson that you should already know from C++ pointers. Don't call functions which expect a pointer with a variable but with the pointer to the variable.
You have mentioned that you are a C/C++ programmer, so I'll make an analogy with C. Your Combine method is similar to strcat in C. Translating your code to C it would look like:
However, "Monday " is a constant string, so what you really would do in C is something like this:
You would have to do a similar thing in Spin.
Dave
That is very interesting. I am glad you showed two examples. I have not tested to see if both examples work in both methods, however, it appears to be the exact same thing, just written differently.
MagIO2
For many years I just hacked my way through it using routines that gave me the desired results. I have no formal education pertaining to programming, whatever I learned, I learned on my own, and did become pretty decent at it. However, pointers have always been my biggest problem pertaining to programming. Before I slowed down with C++, I had a pretty good grasp of using pointers, but that was several years ago. Since then, I have only dabbled in it now and then, for particular projects.
Anyhow guys, thanks alot, I appreciate the "lesson"
Bruce
If you have a look at Mikes code (post #9) you find a good tip how to handle this, it's called naming convention. If you have variables that are meant to keep pointers to memory locations, then add ptr or pointer to the name. The same applies to the names of parameters.
This way you see on a first glance what this variable contains or this parameter expects. If you call a function you can easily decide how to call it:
If the parameter contains ptr you'd either call it by using @variable or by variablePtr (of course this then should contain a valid address).
For return values in most cases you have to know what these will return.
It has been several years since I have seen or done anything close to resembling that. This is more like it
Bruce
strcmp
strcpy
lstrcpy
And my all time favorite
PathAppend(pszPath, pszMore);
So, if I understand you correctly, using DAT would be equivelant to using 'Static' in say (Java) and VAR would be equivelant to a private CLASS variable?
All data in a DAT section is only there once. Even if you have more than one instance of the object. In opposite each object has it's own independent VAR section.
In my post I talked about the STRING(). If you use STRING( "Test" ) in ten different places of your code (same object or different object does not matter), you really have the string "Test" ten times in the memory image of this program.
1) Try not to have any internal buffers in the string handling code. All buffer space must be declared in the "main" program.
2) Allocate enough space. Sbasic defaulted to 80 bytes.
3) Make strings global to save space. But try to code them locally within subroutines as this is good programming practice.
4) @ is the equivalent of a pointer in C. Use @ liberally in code.
So, declare some string space at the beginning of the program. You can call these whatever you like. I've only need three generic strings so far as they are being recycled but in large assembly programs I found 8 was sufficient.
This concatenate is from Kye's string handling code and I would very much hope this does not overrun (unless you have a string > 80 bytes!)
To use this:
or you can mix and match Spin's "string" method
To build a string:
eg
There are a whole lot more string functions in Kye's code. But not the ones I use the most, ie Left and Mid. So I coded them.
And in doing so I discovered a bit of a programming problem that might explain why it has been hard to find these routines. In general terms, a common line of code might be:
returnvariable := function(passedvariable)
and that is fine except when the function needs to know returnvariable as well. So to solve this, pass returnvariable and passvariable to the function and use it like this, ie with no returnvariable :=
These are a series of test routines to use these string functions. Note that @ gets used a lot.
and the point of all this?
Well, the aim is not to have any internal buffer overruns in functions that you call. I find those sorts of bugs hard to find. So there is no internal buffer. The code seems to end up simpler this way:
Kye's code below, with some additions near the end from myself:
Bruce