ID:195050
 
//Title: ReplaceText
//Credit to: Kuraudo
//Contributed by: Kuraudo

/*
replacetext(haystack, needle, replace)

Replaces all occurrences of needle in haystack (case-insensitive)
with replace value.

replaceText(haystack, needle, replace)

Replaces all occurrences of needle in haystack (case-sensitive)
with replace value.
*/



proc
replacetext(haystack, needle, replace)
var
pos = findtext(haystack, needle)
needleLen = length(needle)
replaceLen = length(replace)
while(pos)
haystack = copytext(haystack, 1, pos) + replace + \
copytext(haystack, pos+needleLen)
pos = findtext(haystack, needle, pos+replaceLen)
return haystack

replaceText(haystack, needle, replace)
var
pos = findText(haystack, needle)
needleLen = length(needle)
replaceLen = length(replace)
while(pos)
haystack = copytext(haystack, 1, pos) + replace + \
copytext(haystack, pos+needleLen)
pos = findText(haystack, needle, pos+replaceLen)
return haystack

///*
//Testing Code/Sample Implementation:

client/verb/Test()
var/hamspam = "spamspamspamSPAM"
src << replacetext(hamspam, "spam", "hamspam")
src << replaceText(hamspam, "SPAM", "HAMSPAM")

//*/
Wow, I thought I had uploaded mine a long time ago. It wouldn't serve any purpose uploading it now, of course; the basic replacetext() is hard to improve upon, so it looks exactly like this.
In response to Jtgibson
I have a rather different version, though it still uses a loop.

proc
replacetext( string , fugitive , replace , caseSensitive = 0 )

var/pos = caseSensitive ? findText( string , fugitive ) :\
findtext( string , fugitive )

if( !pos )
return string

var/part1 = copytext( string , 1 , pos ) ,\
part2 = copytext( string , pos + length( fugitive ) )

. = .( part1 + replace + part2 , args[2] , args[3] , args[4] )
// . = .( arglist( list(part1 + replace + part2) + args.Copy(2) ) )
// Ditched the above line. It is most probably less efficient
// than its preceeding line.

replaceText( string , fugitive , replace )

return replacetext( arglist( args ) , 1 )

mob/verb/test()
src << replacetext( "Hi.SPAM" , "SPAM" , "" )
src << replaceText( "LAWLSPAMspam","SPAM" , "" , 1 )


As for its efficiency, I'm not quite sure if calling the proc over and over again or just running one loop is better than the other.

Also, just a small note, that ? operator's second argument, findtext( string , fugitive ) is a bit out of place. It should be located accordingly under the above line. Then again, I guess this isn't the DM compiler and there may be different settings such as resolution affecting it, so I ought not to worry about it.
In response to Metamorphman
Metamorphman wrote:
proc
replacetext( string , fugitive , replace , caseSensitive = 0 )

var/pos = caseSensitive ? findText( string , fugitive ) :\
findtext( string , fugitive )

if( !pos )
return string

var/part1 = copytext( string , 1 , pos ) ,\
part2 = copytext( string , pos + length( fugitive ) )

. = .( part1 + replace + part2 , args[2] , args[3] , args[4] )

Firstly, it's important to note that yours crashes anytime fugitive occurs somewhere in replace. For example:
client/verb/Test()
replacetext("He said hello.", "hello", "(hello)")


Metamorphman wrote:
As for its efficiency, I'm not quite sure if calling the proc over and over again or just running one loop is better than the other.

Using simple looping structures is almost always better than recursive function calls for several reasons, some of which I'll list here:
  • Stack Management: As you call the proc, it has to keep in memory the calling proc, and then start up the new proc, then return to the caller. If the called proc calls out over and over, it nests very deeply, sometimes resulting in a crash, meaning in cases where there are many occurrences of the string to be replaced, yours will crash. For an example:
    #define DEBUG

    proc/recursive(x=0) return x < 65535 ? .(x+1) : x

    client/verb/Test() recursive()
  • Another stack-related problem: When calling a proc, all of the arguments you pass get pushed onto the stack, and then execution jumps to the beginning of the proc. That means for every call to replacetext("a","a","b"), you get this:
    push "b"
    push "a"
    push "a"
    call replacetext
    pop arg1 // arg1 = "a"
    pop arg2 // arg2 = "a"
    pop arg3 // arg3 = "b"

    Instead, if you simply looped, you simply evaluate the test case, and if it's true, you jump to the beginning of the loop.
In response to Kuraudo
Ah, indeed. Thanks for the explanation.