ID:1698990
 
Keywords: lists, sequences, strings
This code is quite old. Unfortunately, it isn't commented or documented. I realised at some point that I didn't like the idea of using negative numbers in the arguments (which this code uses), and I thought it might make more sense to just have left and right versions of the functions.

At the time I was determined to make an ideal library that would replace strings and lists with sequences, similar to how things are in Python, but I kind of lost interest and wanted to work on more important issues instead. I was also worried that it might be too inefficient for general use, which was discouraging.

I was never planning to release the code in this way, but at this point, I'm not sure if I will ever revisit it. I just thought some of you might find it useful or interesting in some way. I must warn you that these functions are very different from the built-in ones, and once you try them, you might not want to go back!

You should be able to figure this out without documentation. Just know that both the start and length arguments can be negative and that they do NOT correspond to the Start and End args of the built-in text functions. It should become clear once you test things out.

#define islist(x) istype(x,/list)

#define setpositionstemplate \
if(length > 0){\
.--;\
if(start < 0){\
startpos = seqlen + start + 1;\
if(. >= -1){\
endpos = seqlen}\
else{\
endpos = seqlen + . + 1}}\
else{\
startpos = start;\
if(. >= seqlen){\
endpos = seqlen}\
else{\
endpos = .}}}\
else if(length < 0){\
.++;\
if(start > 0){\
startpos = start;\
if(. <= 1){\
endpos = 1}\
else{\
endpos = .}}\
else{\
startpos = seqlen + start + 1;\
if(. <= -seqlen){\
endpos = 1}\
else{\
endpos = seqlen + . + 1}}}\
else{\
if(start > 0){\
startpos = start;\
endpos = seqlen}\
else{\
startpos = seqlen + start + 1;\
endpos = 1}}



proc
find(sequence, val, start = 1, length = 0)
if(!(length(sequence) && start)) return 0

var
seqlen = length(sequence)
vallen = length("[val]")
startpos
endpos

if(abs(start) > seqlen) return 0
. = start + length
setpositionstemplate

if(istext(sequence))
if(!(vallen in 1 to seqlen)) return 0
if(startpos <= endpos)
. = findtextEx(sequence, "[val]", startpos, endpos + 1)
else
if(vallen > (startpos - endpos + 1)) return 0
for(var/pos in startpos - vallen to endpos step -1)
. = findtextEx(sequence, "[val]", pos, startpos + 1)
if(.) return

else if(islist(sequence))
if(startpos <= endpos)
. = sequence:Find(val, startpos, endpos + 1)
else
for(var/pos in startpos to endpos step -1)
if(sequence[pos] == val)
return pos
. = 0

else . = 0


findNoCase(sequence, val, start = 1, length = 0)
if(!(length(sequence) && start)) return 0

var
seqlen = length(sequence)
vallen = length("[val]")
startpos
endpos

if(abs(start) > seqlen) return 0
. = start + length
setpositionstemplate

if(istext(sequence))
if(!(vallen in 1 to seqlen)) return 0
if(startpos <= endpos)
. = findtext(sequence, "[val]", startpos, endpos + 1)
else
if(vallen > (startpos - endpos + 1)) return 0
for(var/pos in startpos - vallen to endpos step -1)
. = findtext(sequence, "[val]", pos, startpos + 1)
if(.) return

else if(islist(sequence))
if(istext(val))
if(startpos <= endpos)
for(var/pos in startpos to endpos)
. = sequence[pos]
if(istext(.) && (length(.) == vallen) && findtext(., val))
return pos
. = 0
else
for(var/pos in startpos to endpos step -1)
. = sequence[pos]
if(istext(.) && (length(.) == vallen) && findtext(., val))
return pos
. = 0
else
if(startpos <= endpos)
. = sequence:Find(val, startpos, endpos + 1)
else
for(var/pos in startpos to endpos step -1)
if(sequence[pos] == val)
return pos
. = 0

else . = 0


copy(sequence, start = 1, length = 0)
if(!(length(sequence) && start)) return null

var
seqlen = length(sequence)
startpos
endpos

if(abs(start) > seqlen) return null
. = start + length
setpositionstemplate

if(istext(sequence))
if(startpos <= endpos)
. = copytext(sequence, startpos, endpos + 1)
else
. = copytext(sequence, endpos, startpos + 1)

else if(islist(sequence))
if(startpos <= endpos)
. = sequence:Copy(startpos, endpos + 1)
else
. = sequence:Copy(endpos, startpos + 1)

else . = null


cut(sequence, start = 1, length = 0)
if(!(length(sequence) && start)) return null

var
seqlen = length(sequence)
startpos
endpos

if(abs(start) > seqlen) return null
. = start + length
setpositionstemplate

if(istext(sequence))
if(startpos <= endpos)
. = copytext(sequence, 1, startpos) + copytext(sequence, endpos + 1)
else
. = copytext(sequence, 1, endpos) + copytext(sequence, startpos + 1)

else if(islist(sequence))
var/list/lst = sequence
if(startpos <= endpos)
lst.Cut(startpos, endpos + 1)
else
lst.Cut(endpos, startpos + 1)
. = lst

else . = null


add()
for(var/a in args)
. += a


A large portion of the code was repetitive, so I had used the preprocessor to wrap it up. It was like redirecting a river to flow in another direction. DM can be a stubborn language, and sometimes you have to be a stubborn developer to make it work the way you want.

You may use this however you want.
Oh neat. I didn't know you could use #defines in that manner.
#defines can do a lot of work for you if you know how to use them. In some cases they can replace whole functions, and can be used without the overhead of calling a real one. The reference doesn't have much documentation on the preprocessor.
In response to Mr_Goober
Define literally replaces whatever text it finds with what you specified. Then the file gets compiled after the changes were made. From what I understand.
In response to Alex Peterson
He probably already knows how that works, but a lot of people here are probably not aware of how you can use braces to #define whole blocks of code.
In response to Multiverse7
Multiverse7 wrote:
He probably already knows how that works, but a lot of people here are probably not aware of how you can use braces to #define whole blocks of code.

Yeah, this.
I was referring to how both strings and lists are "types" of sequences in Python, and so they share certain properties, much unlike DM. However, this isn't the best example, and also isn't the larger point that I was trying to make.

A good example of a function comparable to my copy() proc would be the Mid() function. Even then, the Mid() function is not even half as powerful as my copy() proc.

For an actual example of what it can do, all of the following is true:
// copy(sequence, start, length)
copy("something", 1, 0) == "something"// default
copy("something", 1, 4) == "some" // first 4 characters
copy("something", 5) == "thing" // last 5 characters
copy("something", -6) == "some" // first 4 characters
copy("something", -1, -5) == "thing" // last 5 characters
copy("something", -2, 2) == "ng"
copy("something", -2, -2) == "in"
copy("something", 5, 2) == "th"
vs.
// copytext(T, Start, End)
copytext("something", 1, 0) == "something"// default
copytext("something", 1, 5) == "some" // first 4 characters
copytext("something", 5) == "thing" // last 5 characters
copytext("something", 1, -5) == "some" // first 4 characters
copytext("something", -5) == "thing" // last 5 characters
copytext("something", -2) == "ng"
copytext("something", -3, -1) == "in"
copytext("something", 5, 7) == "th"

Don't forget that my procs work with lists as well. find(), findNoCase(), and cut() follow the same "rules" as copy(), so you should be able to figure them out as well. I don't remember, but there might be some issues with lists of lists.

While I'm sure that the code could be improved, I doubt that there is much redundancy. I think it's actually concise for all that it does. It works as a kind of filter to convert one style of string function into another. If I were writing this now, it would probably be a lot more modular, but unfortunately it would also be slower, due to more procedure calls. At the time I tried to get around it with a macro, but macros can't go as far as real procedures can.