HomeUser Control Panel (unavailable in archive)ForumsTutorialsArt GalleryResourcesMaps

SubString returns null when out of string

08-17-2008, 05:52 AM#1
AceHart
Well, yes, it does.
However, consider the following two functions:

Collapse JASS:
function feature1 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        exitwhen c == null
    endloop
    if c == null then
        return "NULL"
    endif
    return "???"
endfunction
Collapse JASS:
function feature2 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        exitwhen c != "a"
    endloop
    if c == null then
        return "NULL"
    endif
    return "???"
endfunction

And some basic call:
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, feature1())
call DisplayTextToPlayer(GetLocalPlayer(), 0, 0, feature2())

Personally, I was expecting both to say "NULL".
However, only the first one does...


Yes, both loops run as expected, three times, ...

As a work-around, changing the final test to c == "" returns the expected result.
Obviously, that fails in the first function.
And shouldn't be needed anyway.


So, why does the c == null test fail in the second function?
Because testing a string against a string instead of null changes the null to the empty string doesn't sound like an explanation.
08-17-2008, 06:45 AM#2
ToukoAozaki
I suspect the underlying string format is actually a null-terminated string. If so, all those unexpected behavior makes sense.

In a null-terminated string, a character value of 0 ('\0', or null) indicates the end of the string. Note that the null I just mentioned is a null "character", not null "string" (so don't be confused).

"apple" = [a][p][p][l][e][\0]
"" = [\0]

So, SubString("aaa",3,4) would return [\0]. That's equivalent to an empty string, "". The null you're checking in Jass is null "string", which denotes no reference to a string. That's a completely different thing.
08-17-2008, 07:49 AM#3
AceHart
> SubString("aaa",3,4) would return [\0]. That's equivalent to an empty string, "".

Well, "" == null fails as expected.
08-17-2008, 10:27 AM#4
Ammorth
It appears that both "" and null are 2 different strings. null takes up the 0 slot on the string table, while "" takes up the 1 slot on the string table. Making a comparison will return false because they are different strings. ( 1 != 0 )

The other interesting thing is that a null string doesn't happen at the end of a string, like thougt, but another character over.

for example, the string "abc"

[a] [b] [c] [""] [null]

using substring to get each char, that is what would come out of that string.

Collapse JASS:
function string2table takes string s returns integer
    return s
    return 0
endfunction

function PrintStringTable takes string s returns nothing
    local integer i = string2table(s)
    call BJDebugMsg("string table for "+s+": "+I2S(i))
endfunction

function isNull takes string s returns boolean
    return s == null or s == ""
endfunction

function feature1 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        if c == null then
            exitwhen true
        endif
    endloop
    if c == null then
        return "NULL"
    endif
    return "???"
endfunction

function feature2 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        if c != "a" then
            exitwhen true
        endif
    endloop
    if c == null then
        return "NULL"
    endif
    return "???"
endfunction

function feature3 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        call PrintStringTable(c)
        if c == null then
            exitwhen true
        endif
    endloop
    call PrintStringTable(c)
    call BJDebugMsg("count :"+I2S(i))
    if c == null then
        return "NULL"
    endif
    return "???"
endfunction

function feature4 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        call PrintStringTable(c)
        if c == "" then
            exitwhen true
        endif
    endloop
    call PrintStringTable(c)
    call BJDebugMsg("count :"+I2S(i))
    if c == "" then
        return "NULL"
    endif
    return "???"
endfunction

function feature5 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        if c == null then
            exitwhen true
        endif
    endloop
    if isNull(c) then
        return "NULL"
    endif
    return "???"
endfunction

function feature6 takes nothing returns string
    local string s = "aaa"
    local string c
    local integer i = 0

    loop
        set c = SubString(s, i, i + 1)
        set i = i + 1
        if c != "a" then
            exitwhen true
        endif
    endloop
    if isNull(c) then
        return "NULL"
    endif
    return "???"
endfunction

//===========================================================================
function InitTrig_Untitled_Trigger_001 takes nothing returns nothing
    //call BJDebugMsg(feature1()) // NULL
    //call BJDebugMsg(feature2()) // ???
    call BJDebugMsg(feature3()) // NULL
    call BJDebugMsg(feature4()) // NULL
    //call BJDebugMsg(feature5()) // NULL
    //call BJDebugMsg(feature6()) // NULL
endfunction

feature1 and feature2 are the same as here, except changes to the if/then/else check to prove it has nothing to do with exitwhen.

feature3 and feature4 are to demonstrate what I said above about the "" and null. The loop even goes 1 farther on the first, since it has to get past the "" and then reach null.

feature5 and feature6 is feature1 and feature2 but using a check for both "" and null. As expected, they both return null since a null string is either "" or null and the isNull() function checks for both.

Conclusions:

Collapse JASS:
null != ""
substring("aaa", 3, 4) == ""
substring("aaa", 4, 5) == null

I guess to check for a nullstring, its best to check
08-17-2008, 11:04 AM#5
Anitarf
Heh, see how one careless assumption can completely prevent you from figuring out what's wrong.
Quote:
Originally Posted by AceHart
Yes, both loops run as expected, three times, ...
A simple debug message displaying the value of i proves this to not be the case. One assumption less and one debug message more and you could have easily figured this out on your own. Not that I'm blaming you or anything, we all make mistakes; I'm just pointing out the lesson that is to be learned from this thread to everybody.
08-17-2008, 11:38 AM#6
AceHart
Well, this assumption is based on countless scripts and functions that all loop until null.

One famous member here though is indeed using exitwhen ... == "" or ... == null in one of his systems, from at least one year ago...
Ah well.

Case closed.
08-17-2008, 12:05 PM#7
Troll-Brain
Btw, why you don't use the string length ?
08-17-2008, 12:15 PM#8
AceHart
To save a function call.