• 🏆 Texturing Contest #33 is OPEN! Contestants must re-texture a SD unit model found in-game (Warcraft 3 Classic), recreating the unit into a peaceful NPC version. 🔗Click here to enter!
  • It's time for the first HD Modeling Contest of 2024. Join the theme discussion for Hive's HD Modeling Contest #6! Click here to post your idea!

[JASS] String Manipulation Library

B22

B22

Level 6
Joined
Sep 29, 2014
Messages
49
Since I posted about Tips & Tricks - Using Abilities as Data Reference for Triggering, I feel it is only right to share some functions to help the implementation of such system.

While some of the functions below can be found in various other libraries, I would like to highlight two which are extremely related to PRAT:
  1. strIndexer() Note: Bribe has already informed me that this a similar functionality exists in other library
    • This function will treat string as array separated by a user-determined separator.
  2. removeWith()
    • This function will remove all string within or outside a user-determined separators.
    • The aim of this function is to allow users to add-in comments in their string data. Especially useful for PRAT since strings filled with only ASCII code can be confusing.


JASS:
library moStrLib /* v1.1 by Mastermind_OverRide
    **********************B**********************B**********************B**********************B
    *
    *   function strSearch(mString, searchString, occurenceNumber)
    *       returns position of the 'occurence Number'nd searched string
    *       negative occurenceNumber will search backwards
    *       occurenceNumber = 0 will return the count of the searchString
    *
    *   function strSplit(mString, splitString, occurenceNumber, includeSplitString, otherSide)
    *       returns string BEFORE the 'occurence Number'nd splitString, AFTER if otherSide = true
    *
    *   function strIndexer(mString, separator, index)
    *       gets the string located at index separated by "separator"
    *       e.g. "Thompshire, Konge, Ban'orak, Jay" with "," separator, will return Thompshire at index 0, Ban'orak at index 2, etc
    *
    *   function replaceSubStr(mString, oriString, newString, replaceCount, startIndex)
    *       replaces "replaceCount"s oriString with newString
    *       replaceCount = 0 replaces all, negative replaces from last occurence, positive normal behaviour
    *       searches for oriString from startIndex. on negative replaceCount, start index counts from behind 
    *
    *   function removeSubStr(mString, unwantedString, removeCount, startIndex)
    *       just calling replaceSubStr with empty string for newString
    *
    *   function replaceAllSubStr(mString, oriString, newString)
    *       replaces all occurence of oriString with newString
    *
    *   function removeAllSubStr(mString, unwantedString)
    *       just calling replaceAllSubStr with empty string for newString
    *
    *   function removeAllColors(mString, unwantedString)
    *       removes all color code definition in string
    *
    *   function cleanAdjacentSpaces(mString)
    *       removee extra spaces that are adjacent to each other
    *       e.g. "Prince  Arthas     Menethil" -> "Prince Arthas Menethil"
    *
    *   //Added in v1.1
    *   function removeWith(mString, sStart, sEnd, within, include, addExtraPer)
    *       remove all character between instances of sStart and sEnd
    *       on within = false, remove all outside
    *       addExtraPer will append another string that can be used as separator as within=false, will tend to remove these
    *       include will include sStart and sEnd on the resulting string
    *       e.g.
    *       "belv (Boots of Quel'Thalas), bspd (Boots of Speed), afac (Alleria's Flute of Accuracy), clsd (Cloak of Shadows)"
    *       sStart=" (", sEnd=")", within = true; "belv, bspd, afac, clsd"
    *       sStart="(", sEnd=")", within = true; "belv , bspd , afac , clsd "
    *       sStart="(", sEnd=")", within = true; "belv , bspd , afac , clsd "
    *       sStart="(", sEnd=")", within = false; "Boots of Quel'ThalasBoots of SpeedAlleria's Flute of AccuracyCloak of Shadows"
    *       sStart="(", sEnd=")", within = false, addExtraPer=", "; "Boots of Quel'Thalas, Boots of Speed, Alleria's Flute of Accuracy, Cloak of Shadows"
    *
    **********************B**********************B**********************B**********************B*/
    public function strSearch takes string mString, string searchString, integer occurenceNumber, integer startIndex returns integer
        local string tmpStr = ""
        local integer mLength = StringLength(mString)
        local integer sLength = StringLength(searchString)
        local integer count = 0
        local integer i = 0
        
        if occurenceNumber < 0 then
            set i = mLength - startIndex
            set occurenceNumber = -occurenceNumber
            loop
                exitwhen i - sLength < 0
                set tmpStr = SubString(mString, i - sLength, i)
                if tmpStr == searchString then
                    set count = count + 1
                    if count == occurenceNumber then
                        return i - sLength
                    endif
                endif
                set i = i - 1
            endloop
        else
            set i = 0 + startIndex
            loop
                exitwhen i + sLength > mLength
                set tmpStr = SubString(mString, i, i + sLength)
                if tmpStr == searchString then
                    set count = count + 1
                    if count == occurenceNumber then
                        return i
                    endif
                endif
                set i = i + 1
            endloop
        endif
        //Return number of searchString afterloop or -1 indicating no substring
        if occurenceNumber == 0 then
            return count
        else
            return -1
        endif
    endfunction
    public function strSplit takes string mString, string splitString, integer occurenceNumber, boolean includeSplitString, boolean otherSide returns string
        local integer tmpInt = strSearch(mString, splitString, occurenceNumber, 0)
        if otherSide then
            if includeSplitString then
                return SubString(mString, tmpInt, StringLength(mString))
            else
                return SubString(mString, tmpInt + StringLength(splitString), StringLength(mString))
            endif
        else
            if includeSplitString then
                return SubString(mString, 0, tmpInt + StringLength(splitString))
            else
                return SubString(mString, 0, tmpInt)
            endif
        endif
    endfunction
    public function strIndexer takes string mString, string separator, integer index returns string
        local string tmpStr = ""
        local integer mLength = StringLength(mString)
        local integer sLength = StringLength(separator)
        local integer count = 0
        local integer lastIndex = 0
        local integer i = 0
        
        loop
            exitwhen i + sLength > mLength
            set tmpStr = SubString(mString, i, i + sLength)
            if tmpStr == separator or i == mLength then
                if count == index then
                    return SubString(mString, lastIndex, i)
                endif
                set count = count + 1
                set i = i + sLength
                set lastIndex = i
            else
                set i = i + 1
            endif
        endloop
        return ""
    endfunction
    public function replaceSubStr takes string mString, string oriString, string newString, integer replaceCount, integer startIndex returns string
        local string tmpStr = ""
        local string finalString = ""
        local integer mLength = StringLength(mString)
        local integer oLength = StringLength(oriString)
        local integer sLength = StringLength(newString)
        local integer lastIndex = 0
        local integer i = 0
        local integer count = 0
        local integer maxCount = 0

        if replaceCount < 0 then
            set i = mLength - startIndex
            set maxCount = -replaceCount
            loop
                exitwhen i - sLength < 0
                set tmpStr = SubString(mString, i - oLength, i)
                //i == 0 to immediately append the rest of the string at the end
                if tmpStr == oriString or i == 0 then
                    //Appending to the front instead
                    set finalString = newString + SubString(mString, i, lastIndex) + finalString
                    //Skip oLength characters to speed up loop
                    set lastIndex = i - oLength
                    set i = lastIndex
                    set count = count + 1
                    if count == maxCount then
                        set finalString = newString + SubString(mString, 0, lastIndex) + finalString
                        exitwhen true
                    endif
                else
                    set i = i - 1
                endif
            endloop
        else
            set i = 0 + startIndex
            if replaceCount == 0 then
                set maxCount = StringLength(mString)
            else
                set maxCount = replaceCount
            endif
            loop
                exitwhen i > mLength
                set tmpStr = SubString(mString, i, i + oLength)
                //i == mLength to immediately append the rest of the string at the end
                if tmpStr == oriString or i == mLength then
                    set finalString = finalString + SubString(mString, lastIndex, i) + newString
                    //Skip oLength characters to speed up loop
                    set lastIndex = i + oLength
                    set i = lastIndex
                    set count = count + 1
                    if count == maxCount then
                        set finalString = finalString + SubString(mString, lastIndex, mLength) + newString
                        exitwhen true
                    endif
                else
                    set i = i + 1
                endif
            endloop
        endif
        return finalString
    endfunction
    
    public function removeSubStr takes string mString, string unwantedString, integer removeCount, integer startIndex returns string
        return replaceSubStr(mString, unwantedString, "", removeCount, startIndex)
    endfunction
    public function replaceAllSubStr takes string mString, string oriString, string newString returns string
        return replaceSubStr(mString, oriString, newString, 0, 0)
    endfunction
    
    public function removeAllSubStr takes string mString, string unwantedString returns string
        return replaceAllSubStr(mString, unwantedString, "")
    endfunction
    public function removeAllColors takes string mString returns string
        local string tmpStr = ""
        local string finalString = ""
        local integer mLength = StringLength(mString)
        local integer lastIndex = 0
        local integer i = 0
        loop
            exitwhen i > mLength
            set tmpStr = SubString(mString, i, i + 2)
            //String Case to ensure that both |c and |C are detected
            if StringCase(tmpStr, false) == "|c" or i == mLength then
                set finalString = finalString + SubString(mString, lastIndex, i)
                set lastIndex = i + 10
                set i = lastIndex
            else
                if tmpStr == "|r" or i == mLength then
                    set finalString = finalString + SubString(mString, lastIndex, i)
                    set lastIndex = i + 2
                    set i = lastIndex
                else
                    set i = i + 1
                endif
            endif
        endloop
        return finalString
    endfunction
    
    public function cleanAdjacentSpaces takes string mString returns string
        //This function could realistically remove any adjacent duplicates, but I see little reason to use it outside of spaces
        local string tmpStr = ""
        local string finalString = ""
        local integer mLength = StringLength(mString)
        local integer lastIndex = 0
        local integer i = 0
        local boolean inSearch = false
        loop
            exitwhen i > mLength
            set tmpStr = SubString(mString, i, i + 1)
            //Check first space
            if (tmpStr == " " and inSearch == false) or i == mLength then
                if inSearch == true then
                    set lastIndex = i
                endif
                set inSearch = true
                set finalString = finalString + SubString(mString, lastIndex, i + 1)
            endif
            //Check if next few letters are also spaces
            if tmpStr != " " and inSearch == true then
                //Tell to stop searching if it is not a space
                set inSearch = false
                set lastIndex = i
            endif
            set i = i + 1
        endloop
        return finalString
    endfunction
    
    //Added in v1.1
    public function removeWith takes string mString, string sStart, string sEnd, boolean within, boolean include, string addExtraPer returns string
        local string tmpStr = ""
        local string finalString = ""
        local integer mLength = StringLength(mString)
        local integer sStaLength = StringLength(sStart)
        local integer sEndLength = StringLength(sEnd)
        local integer i = 0
        local integer lastIndex = 0
        local boolean inSearch = false
        if within then
            set sStaLength = StringLength(sStart)
            set sEndLength = StringLength(sEnd)
        else
            set tmpStr = sStart
            set sStart = sEnd
            set sEnd = tmpStr
            set sStaLength = StringLength(sStart)
            set sEndLength = StringLength(sEnd)
            set inSearch = true
        endif
        loop
            exitwhen i > mLength
            if not inSearch then
                set tmpStr = SubString(mString, i, i + sStaLength)
                if tmpStr == sStart or i == mLength then
                    set inSearch = true
                    if include then
                        set finalString = finalString + SubString(mString, lastIndex, i + sStaLength)
                    else
                        set finalString = finalString + SubString(mString, lastIndex, i)
                    endif
                    if i != mLength -1 then
                        set finalString = finalString + addExtraPer
                    endif
                endif
            else
                set tmpStr = SubString(mString, i, i + sEndLength)
                if tmpStr == sEnd or i == mLength then
                    set inSearch = false
                    if include then
                        set lastIndex = i
                    else
                        set lastIndex = i + sEndLength
                    endif
                endif
            endif
            
            set i = i + 1
        endloop
        return finalString
    endfunction
endlibrary

This one is a bonus, just some stuffs I used to make some cinematic texts adaptable to a changing context.
Can never be perfect, but enough to perform a convincing enough job I think.
JASS:
library moTextLib requires moStrLib /* v1.0.1 by Mastermind_OverRide
    **********************B**********************B**********************B**********************B
    *
    *   function textPoss(mainString)
    *       returns possessive form, checks is last letter is "s", e.g. Kael -> Kael's
    *
    *   function textPlural(mainString)
    *       returns possessive form, e.g. Footman -> Footmen, etc
    *
    *   function textDemonym(mainString)
    *       returns e.g. Orc -> Orcish, Night Elf -> Night Elven, etc
    *
    *   function autoArticulations(mainString)
    *       fixes "a"s and "an"s in text contextually
    *       //v1.0.1 - Added checkSpeceNull to prevent affecting unrelated texts ending with "a" or "an"
    *
    *   function textDeleteSpace(mainString, last)
    *       removes, first or last space. best done after moStrLib_cleanAdjacentSpaces
    *       do nothing if not exist
    *
    *   function changeCaseFirst(mainString, upCase)
    *       change the of only the first letter, upCase true for uppercase, flase for lowercase
    *
    **********************B**********************B**********************B**********************B*/
    
    //Private Functions
    
    private function getFirst takes string mString, integer length returns string
        local integer tmpInt = StringLength(mString)
        return StringCase(SubString(mString, 0, length), false)
    endfunction
    private function getLast takes string mString, integer length returns string
        local integer tmpInt = StringLength(mString)
        return StringCase(SubString(mString, tmpInt - length, tmpInt), false)
    endfunction
    private function beforeLast takes string mString, integer length, integer fromLast returns string
        local integer tmpInt = StringLength(mString)
        set tmpInt = tmpInt - (fromLast + 1)
        return StringCase(SubString(mString, tmpInt - length, tmpInt), false)
    endfunction
    
    private function cutFirst takes string mString, integer length returns string
        return SubString(mString, length, StringLength(mString))
    endfunction
    private function cutLast takes string mString, integer length returns string
        local integer tmpInt = StringLength(mString)
        return SubString(mString, 0, tmpInt - length)
    endfunction
    private function vowelCheck takes string mString returns boolean
        local string tmpStr = StringCase(mString, false)
        if StringLength(tmpStr) > 1 then
            set tmpStr = getFirst(tmpStr, 1)
        endif
        if tmpStr == "a" then
            return true
        endif
        if tmpStr == "e" then
            return true
        endif
        if tmpStr == "i" then
            return true
        endif
        if tmpStr == "o" then
            return true
        endif
        if tmpStr == "u" then
            return true
        endif
        return false
    endfunction
    //Public Functions
    public function textPoss takes string mString returns string
        //General Patterns
        if getLast(mString, 1) == "s" then
            return mString + "'"
        endif
        return mString + "'s"
    endfunction
    public function textPlural takes string mString returns string
        local boolean tmpBool = false
        if StringLength(mString) > 1 then
            //Special Patterns
            if getLast(mString, 5) == "mouse" or getLast(mString, 5) == "louse" then
                return cutLast(mString, 4) + "ice"
            endif
            if getLast(mString, 3) == "die" then
                return cutLast(mString, 1) + "ce"
            endif
            if getLast(mString, 4) == "foot" then
                return cutLast(mString, 4) + moStrLib_replaceAllSubStr(getLast(mString, 4), "o", "e")
            endif
            if getLast(mString, 5) == "tooth" or getLast(mString, 5) == "goose" then
                return cutLast(mString, 5) + moStrLib_replaceAllSubStr(getLast(mString, 5), "o", "e")
            endif
        
            //General Patterns
            //-- Rifleman -> Riflemen
            if getLast(mString, 3) == "man" then
                return cutLast(mString, 2) + "en"
            endif
            //-- Leaf -> Leaves
            if getLast(mString, 1) == "f" or getLast(mString, 2) == "e" then
                return cutLast(mString, 1) + "ves"
            endif
            //-- Fungus -> Fungi / Necropolis -> Necropoli / Magus -> Magi
            set tmpBool = false
            set tmpBool = tmpBool or getLast(mString, 2) == "is"
            set tmpBool = tmpBool or getLast(mString, 2) == "os"
            set tmpBool = tmpBool or getLast(mString, 2) == "us"
            if tmpBool then
                return cutLast(mString, 2) + "i"
            endif
            //-- Huntress -> Huntresses
            set tmpBool = false
            set tmpBool = tmpBool or getLast(mString, 1) == "s"
            set tmpBool = tmpBool or getLast(mString, 1) == "x"
            set tmpBool = tmpBool or getLast(mString, 1) == "o"
            set tmpBool = tmpBool or getLast(mString, 1) == "z"
            set tmpBool = tmpBool or getLast(mString, 2) == "ch"
            set tmpBool = tmpBool or getLast(mString, 2) == "sh"
            if tmpBool then
                return mString + "es"
            endif
        endif
        
        //Normal
        return mString + "s"
    endfunction
    public function textDemonym takes string mString returns string
        local boolean tmpBool = false
        
        //-- Human -> Human
        if getLast(mString, 3) == "man" then
            return mString
        endif
        //-- Dragon -> Draconic
        if getLast(mString, 6) == "dragon" then
            return cutLast(mString, 3) + "conic"
        endif
        //-- Demon -> Demonic
        if getLast(mString, 5) == "demon" or getLast(mString, 5) == "titan" then
            return mString + "ic"
        endif
        //-- Skeleton -> Skeletal
        if getLast(mString, 8) == "skeleton" then
            return cutLast(mString, 2) + "al"
        endif
        //-- Orc -> Orcish
        if getLast(mString, 3) == "orc" then
            return mString + "ish"
        endif
        //-- Elf -> Elves
        if getLast(mString, 1) == "f" then
            return cutLast(mString, 1) + "ven"
        endif
        //-- Plagueland -> Plaguelander
        if getLast(mString, 4) == "land" then
            return mString + "er"
        endif
        //Ended with 'S'
        //-- Darnassus -> Darnassian
        set tmpBool = getLast(mString, 1) == "s"
        set tmpBool = tmpBool and vowelCheck(beforeLast(mString, 1, 0))
        set tmpBool = tmpBool and beforeLast(mString, 1, 1) == "s"
        if tmpBool then
            return cutLast(mString, 2) + "ian"
        endif
        //-- Glineas -> Gilnean / Kul Tiras -> Kul Tiran
        set tmpBool = getLast(mString, 1) == "s"
        set tmpBool = tmpBool and vowelCheck(beforeLast(mString, 1, 0))
        set tmpBool = tmpBool and vowelCheck(beforeLast(mString, 1, 1))
        if tmpBool then
            return cutLast(mString, 1) + "n"
        endif
        //-- Quel'Thalas -> Thalassian / Kul Tiras -> Tirassian
        set tmpBool = getLast(mString, 1) == "s"
        if getLast(mString, 1) == "s" then
            return mString + "sian"
        endif
        //Strom -> Stromic //
        if getLast(mString, 1) == "m" then
            return mString + "ic"
        endif
        //Normal
        if getLast(mString, 1) == "a" then
            return mString + "n"
        endif
        if getLast(mString, 1) == "e" then
            return cutLast(mString, 1) + "ian"
        endif
        if vowelCheck(getLast(mString, 1)) then
            return mString + "an"
        endif
        return mString + "ian"
    endfunction
    private function checkSpaceNull takes string cString returns boolean
        if StringLength(cString) <= 0 then
            return true
        endif
        if cString == " " then
            return true
        endif
        return false
    endfunction
    public function autoArticulations takes string mString returns string
        local string tmpStr = ""
        local string prevChar = ""
        local string finalString = ""
        local boolean tmpBool = false
        local integer mLength = StringLength(mString)
        local integer i = 0
        local integer lastIndex = 0
        loop
            exitwhen i > mLength
            set tmpStr = StringCase(SubString(mString, i, i + 2), false)
            set prevChar = StringCase(SubString(mString, i - 1, i), false)
            //v1.0.1 - Added checkSpeceNull to prevent affecting unrelated texts ending with "a" or "an"
            if tmpStr == "a " and checkSpaceNull(prevChar) then
                set tmpStr = SubString(mString, i + 2, i + 3)
                if vowelCheck(tmpStr) then
                    set finalString = finalString + SubString(mString, lastIndex, i + 1) + "n "
                    set lastIndex = i + 2
                endif
            endif
            set tmpStr = StringCase(SubString(mString, i, i + 3), false)
            if tmpStr == "an " and checkSpaceNull(prevChar) then
                set tmpStr = SubString(mString, i + 3, i + 4)
                if not vowelCheck(tmpStr) then
                    set finalString = finalString + SubString(mString, lastIndex, i + 1)
                    set lastIndex = i + 2
                endif
            endif
            
            if i == mLength then
                set finalString = finalString + SubString(mString, lastIndex, i)
            endif
            set i = i + 1
        endloop
        return finalString
    endfunction
    public function textDeleteSpace takes string mString, boolean last returns string
        if last == true then
            if getFirst(mString, 1) == " " then
                return cutFirst(mString, 1)
            endif
        else
            if getLast(mString, 1) == " " then
                return cutLast(mString, 1)
            endif
        endif
        return mString
    endfunction
    public function changeCaseFirst takes string mString, boolean upCase returns string
        return StringCase(getFirst(mString, 1), upCase) + cutFirst(mString, 1)
    endfunction
    
endlibrary
 
Last edited:
Have you had a look at any of the existing String systems?



I can't expect anyone to be able to read what Nestharus posted on GitHub, but here you go (some formatting will be messed up due to changes in bbcode syntax):

String Parser
as easy as String.parse("1 2 3")



JASS:
library StringParser uses Ascii
//Ascii- hiveworkshop.com/forums/submissions-414/snippet-ascii-190746/
////////////////////////////////////////////////////////////////////////
//Version: 2.2.2.3
//Author: Nestharus
//
//Characters:
//  Delimiters: " ", ","
//  String Delimiter: "
//  Escape Character: \ (escapes only ")
//  Stack characters: ( ), { }, [ ]
//  Ascii Integer Delimiter: '
////////////////////////////////////////////////////////////////////////
//API
//      function S2B takes string s returns boolean
//      function B2S takes boolean val returns string
//
//      struct StringValue
//          readonly boolean boolean
//          integer integer
//          readonly string string
//          readonly StringType type
//
//          static method operator [] takes string s returns StringValue
//          static method create takes string value, StringType valueType, integer convertedValue returns StringValue
//          method destroy takes nothing returns nothing
//
//      struct StringType
//          static constant integer NULL
//          static constant integer BOOLEAN
//          static constant integer ASCII
//          static constant integer INTEGER
//          static constant integer REAL
//          static constant integer STRING
//          static constant integer STACK
//
//          readonly string name
//          readonly StringType extends
//
//          static method create takes string name, StringType extend returns thistype
//          method is takes StringType ofType returns boolean
//
//      struct StringStack
//          readonly StringStack next
//          readonly StringStack stack
//          readonly string value
//          readonly integer size
//          readonly integer count
//          readonly StringType type
//
//          method toString takes nothing returns string
//          method pop takes nothing returns thistype
//          method destroy takes nothing returns nothing
//
//      struct String
//          static method filter takes string toFilter, string filterChar, boolean onlyAtStart returns string
//          static method parse takes string val returns StringStack
//          static method typeof takes string s returns StringType
////////////////////////////////////////////////////////////////////////
    globals
        private integer stringValueCount = 0
        private integer stringValueRecycleCount = 0
        private integer array stringValueRecycle
       
        private string array stringValues
        private integer array stringConvertValue
        private integer array stringValueTypes
        private integer array stringValueLength
        private hashtable stringValueIds = InitHashtable()
        private integer array stringValueId
       
        private string array stringTypeNames
        private integer stringTypeCount
        private integer array stringTypeExtend
        private integer array reverseStringTypeExtend
       
        private StringStack array stackNext
        private string array stackValue
        private integer array stackCount
        private integer array stackStringType
        private integer stackInstanceCount = 0
        private integer array stackRecycle
        private integer stackRecycleCount = 0
        private integer array stackStack
        private integer array stackSize
    endglobals
   
    private function FilterCharacter takes string stringToFilter, string char, boolean onlyAtStart returns string
        local integer count = 0
        local integer length = StringLength(stringToFilter)
        local string newString = ""
        local string charCheck
        if (onlyAtStart) then
            loop
                exitwhen SubString(stringToFilter, count, count+1) != char
                set count = count + 1
            endloop
            set newString = SubString(stringToFilter, count, length)
        else
            loop
                exitwhen count == length
                set charCheck = SubString(stringToFilter, count, count+1)
                if (charCheck != char) then
                    set newString = newString + charCheck
                endif
                set count = count + 1
            endloop
        endif
       
        return newString
    endfunction
   
    private function Typeof takes string val returns StringType
        local integer length //length of the string
        local integer length2
        local string char //current character being checked
        local string char2
        local integer curType = 0 //current type to be returned
       
        local boolean foundDecimal //found a decimal place
        local boolean foundNeg //found a negative sign
        local boolean foundInt //found an integer
        local boolean escapeOn //escape is on
        local boolean escaping //currently escaping
       
        local integer id
       
        if (val != null) then
            set curType = stringValueTypes[LoadInteger(stringValueIds, StringHash(val), 0)]
           
            if (curType == 0) then
                set length = StringLength(val)
                set char = SubString(val, 0, 1)
                set char2 = SubString(val, length-1, length)
               
                if (char == "(" or char == "{" or char == "[") then
                    if ((char == "(" and char2 == ")") or (char == "{" and char2 == "}") or (char == "[" and char2 == "]")) then
                        set curType = StringType.STACK
                    endif
                else
                    set curType = StringType.ASCII
                    if ((length != 3 and length != 6) or char != "'" or char2 != "'") then
                        if (char == "\"") then
                            set curType = StringType.STRING
                            set length2 = 1
                            set escapeOn = false
                            set escaping = false
                            loop
                                if (length2 == length) then
                                    return StringType.NULL
                                endif
                                set char = SubString(val, length2, length2+1)
                                if (not escapeOn) then
                                    if (char =="\\") then
                                        set escapeOn = true
                                        set escaping = true
                                    else
                                        exitwhen char == "\""
                                    endif
                                endif
                               
                                if (not escaping) then
                                    set escapeOn = false
                                else
                                    set escaping = false
                                endif
                                set length2 = length2 + 1
                            endloop
                        else
                            set curType = StringType.INTEGER
                            set foundDecimal = false
                            set foundNeg = false
                            set foundInt = false
                           
                            loop
                                exitwhen length == 0
                                set char = SubString(val, length-1, length)
                                if (foundNeg) then
                                    return StringType.NULL //no more parsing necessary
                                elseif (char != "0" and char != "1" and char != "2" and char != "3" and char != "4" and char != "5" and char != "6" and char != "7" and char != "8" and char != "9") then
                                    if (char == "-" and foundInt) then
                                        set foundNeg = true
                                    elseif (char == "." and not foundDecimal) then
                                        set curType = StringType.REAL
                                        set foundDecimal = true
                                    else
                                        return StringType.NULL
                                    endif
                                else
                                    set foundInt = true
                                endif
                                set length = length - 1
                            endloop
                        endif
                    endif
                endif
            endif
        endif
       
        return curType
    endfunction
   
    struct StringStack extends array
        public method operator next takes nothing returns thistype
            return stackNext[this]
        endmethod
       
        public method operator value takes nothing returns string
            return stackValue[this]
        endmethod
       
        public method operator stack takes nothing returns thistype
            return stackStack[this]
        endmethod
       
        public method operator count takes nothing returns integer
            return stackCount[this]
        endmethod
       
        public method operator size takes nothing returns integer
            return stackSize[this]
        endmethod
       
        public method operator type takes nothing returns StringType
            return stackStringType[this]
        endmethod
       
        public method toString takes nothing returns string
            local string s = null
            loop
                exitwhen this == 0
                if (stackValue[this] != null and stackValue[this] != " " and stackValue[this] != "," and stackValue[this] != "") then
                    if (s == null) then
                        set s = stackValue[this]
                    else
                        set s = s + " " + stackValue[this]
                    endif
                endif
                set this = stackNext[this]
            endloop
            return s
        endmethod
       
        public method destroy takes nothing returns nothing
            local thistype array stacks
            local integer i = 0
            set stacks[0] = this
            loop
                exitwhen stacks[I] == 0 and i == 0
                
                loop
                    exitwhen stacks[I].type != StringType.STACK
                    set i = i + 1
                    set stacks[I] = stacks[i-1].stack
                endloop
                if (stacks[I] == 0) then
                    set i = i - 1
                endif
                
                set stackRecycle[stackRecycleCount] = stacks
[I]                set stackRecycleCount = stackRecycleCount + 1
                set stacks[I] = stacks[I].next
            endloop
        endmethod
        
        public method pop takes nothing returns thistype
            set stackRecycle[stackRecycleCount] = this
            set stackRecycleCount = stackRecycleCount + 1
            if (type == StringType.STACK) then
                call stack.destroy()
            endif
            return next
        endmethod
    endstruct
    
    struct String extends array
        public static method filter takes string toFilter, string filterChar, boolean onlyAtStart returns string
            return FilterCharacter(toFilter, filterChar, onlyAtStart)
        endmethod
        
        public static method parse takes string val returns StringStack
            local StringStack this
            local StringStack array last
            local string array openStack
            local integer stack = 0
            local integer start
            local integer finish
            local string char
            local integer length
            local boolean found
            
            local boolean escaping
            local boolean escaped
            
            local boolean foundDecimal
            local boolean foundNeg
            local boolean foundInt
            
            local integer array totalCount
            
            local integer tyepCheck
            
            local integer length2
            
            local StringType curType
            
            local boolean done = false
            
            if (val != null and val != "") then
                set this = 0
                set length = StringLength(val)
                set finish = 0
                loop
                    set found = false
                    set curType = -1
                    loop
                        set finish = finish + 1
                        set char = SubString(val,finish-1, finish)
                        if (char != " " and char != ",") then
                            set start = finish-1
                            set found = true
                        endif
                        exitwhen found or finish == length
                    endloop
                    
                    exitwhen not found
                    
                    if (char == "(" or char == "{" or char == "[") then
                        if (stackRecycleCount != 0) then
                            set stackRecycleCount = stackRecycleCount - 1
                            set this = stackRecycle[stackRecycleCount]
                        else
                            set stackInstanceCount = stackInstanceCount + 1
                            set this = stackInstanceCount
                        endif
                        
                        set totalCount[stack] = totalCount[stack] + 1
                        set stackStringType[this] = StringType.STACK
                        set stackNext[last[stack]] = this
                        set last[stack] = this
                        set stack = stack + 1
                        set openStack[stack] = char
                        set last[stack] = this
                        set stackNext[last[stack]] = 0
                        set totalCount[stack] = 0
                    elseif (char == ")" or char == "}" or char == "]") then
                        if (stack > 0 and ((openStack[stack] == "(" and char == ")") or (openStack[stack] == "{" and char == "}") or (openStack[stack] == "[" and char == "]"))) then
                            set stack = stack - 1
                            set stackStack[last[stack]] = stackNext[last[stack]]
                            set stackSize[last[stack]] = totalCount[stack+1]
                            set stackNext[last[stack]] = 0
                        else
                            set stackNext[last[stack]] = 0
                            set stack = 0
                            set last[0] = stackNext[0]
                            loop
                                exitwhen last[stack] == 0 and stack == 0
                                
                                loop
                                    exitwhen last[stack].type != StringType.STACK
                                    set stack = stack + 1
                                    set last[stack] = last[stack-1].stack
                                endloop
                                if (last[stack] == 0) then
                                    set stack = stack - 1
                                endif
                                
                                set stackRecycle[stackRecycleCount] = last[stack]
                                set stackRecycleCount = stackRecycleCount + 1
                                set last[stack] = stackNext[last[stack]]
                            endloop
                            return 0
                        endif
                    else
                        if (char == "\"" and length-finish > 0) then
                            set escaped = false
                            set escaping = false
                            loop
                                set finish = finish + 1
                                set char = SubString(val, finish-1, finish)
                                
                                if (not escaped) then
                                    if (char == "\"") then
                                        set curType = StringType.STRING
                                        exitwhen true
                                    elseif (char == "\\") then
                                        set val = SubString(val, 0, finish-1)+SubString(val, finish, length)
                                        set length = length - 1
                                        set finish = finish - 1
                                        if (finish == start) then
                                            set start = start - 1
                                        endif
                                        set escaped = true
                                        set escaping = true
                                    endif
                                endif
                                
                                if (not escaping) then
                                    set escaped = false
                                else
                                    set escaping = false
                                endif
                                exitwhen finish == length
                            endloop
                            if (curType == -1) then
                                set curType = 0
                            endif
                        elseif (char == "'") then
                            if (length-finish > 4 and SubString(val, finish+4, finish+5) == "'") then
                                set finish = finish + 5
                                set curType = StringType.ASCII
                            elseif (length-finish > 1 and SubString(val, finish+1, finish+2) == "'") then
                                set finish = finish + 2
                                set curType = StringType.ASCII
                            endif
                            if (curType == -1) then
                                set curType = 0
                            endif
                        else
                            loop
                                exitwhen char == " " or char == "," or char == "(" or char == ")" or char == "{" or char == "}" or char == "[" or char == "]" or char == "\"" or char == "'"
                                set done = finish == length
                                exitwhen done
                                set finish = finish + 1
                                set char = SubString(val, finish-1, finish)
                            endloop
                            if (not done) then
                                set finish = finish - 1
                                set char = SubString(val, finish-1, finish)
                            endif
                        endif
                        
                        if (stackRecycleCount != 0) then
                            set stackRecycleCount = stackRecycleCount - 1
                            set this = stackRecycle[stackRecycleCount]
                        else
                            set stackInstanceCount = stackInstanceCount + 1
                            set this = stackInstanceCount
                        endif
                        
                        set totalCount[stack] = totalCount[stack] + 1
                        set stackNext[last[stack]] = this
                        set last[stack] = this
                        
                        set stackValue[this] = SubString(val, start, finish)
                        
                        if (curType == -1) then
                            set curType = LoadInteger(stringValueIds, StringHash(stackValue[this]), 0)
                            if (curType != 0) then
                                set stackStringType[this] = stringValueTypes[curType]
                            else //parse number
                                set curType = StringType.INTEGER
                                set foundDecimal = false
                                set foundNeg = false
                                set foundInt = false
                                set length2 = finish
                                
                                loop
                                    exitwhen length2 == start
                                    set char = SubString(val, length2-1, length2)
                                    if (foundNeg) then
                                        set curType = StringType.NULL
                                        exitwhen true
                                    elseif (char != "0" and char != "1" and char != "2" and char != "3" and char != "4" and char != "5" and char != "6" and char != "7" and char != "8" and char != "9") then
                                        if (char == "-" and foundInt) then
                                            set foundNeg = true
                                        elseif (char == "." and not foundDecimal) then
                                            set curType = StringType.REAL
                                            set foundDecimal = true
                                        else
                                            set curType = StringType.NULL
                                            exitwhen true
                                        endif
                                    else
                                        set foundInt = true
                                    endif
                                    set length2 = length2 - 1
                                endloop
                                set stackStringType[this] = curType
                            endif
                        else
                            set stackStringType[this] = curType
                        endif
                        
                        if (stackStringType[this] == StringType.ASCII or stackStringType[this] == StringType.STRING) then
                            set stackValue[this] = SubString(stackValue[this], 1, StringLength(stackValue[this])-1)
                        endif
                    endif
                    exitwhen finish == length
                endloop
                
                set stackNext[last[stack]] = 0
                if (stack == 0) then
                    set stack = 0
                    set last[0] = stackNext[0]
                    loop
                        exitwhen last[stack] == 0 and stack == 0
                        
                        if (last[stack].type == StringType.STACK) then
                            set stack = stack + 1
                            set totalCount[stack] = stackSize[stack]
                            set last[stack] = last[stack-1].stack
                        endif
                        if (last[stack] == 0) then
                            set stack = stack - 1
                        endif
                        
                        set stackCount[last[stack]] = totalCount[stack]
                        set totalCount[stack] = totalCount[stack] - 1
                        set last[stack] = stackNext[last[stack]]
                    endloop
                    return stackNext[0]
                endif
                
                set stack = 0
                set last[0] = stackNext[0]
                loop
                    exitwhen last[stack] == 0 and stack == 0
                    
                    loop
                        exitwhen last[stack].type != StringType.STACK
                        set stack = stack + 1
                        set last[stack] = last[stack-1].stack
                    endloop
                    if (last[stack] == 0) then
                        set stack = stack - 1
                    endif
                    
                    set stackRecycle[stackRecycleCount] = last[stack]
                    set stackRecycleCount = stackRecycleCount + 1
                    set last[stack] = stackNext[last[stack]]
                endloop
            endif
            
            return 0
        endmethod
        
        public static method typeof takes string s returns StringType
            return Typeof(s)
        endmethod
    endstruct
    
    //string to boolean
    function S2B takes string s returns boolean
        return stringConvertValue[LoadInteger(stringValueIds, StringHash(s), 0)] > 0
    endfunction
    
    //boolean to string
    function B2S takes boolean val returns string
        if (val) then
            return stringValues[1]
        endif
        return stringValues[2]
    endfunction
    
    struct StringValue extends array
        public method destroy takes nothing returns nothing
            if (stringValueLength[this] > 0) then
                set stringValueRecycle[stringValueRecycleCount] = this
                set stringValueRecycleCount = stringValueRecycleCount + 1
                
                set stringValueLength[this] = 0
                call RemoveSavedInteger(stringValueIds, stringValueId[this], 0)
            endif
        endmethod
        
        public static method create takes string value, StringType valueType, integer convertedValue returns thistype
            local integer id = StringHash(value)
            local thistype this = LoadInteger(stringValueIds, id, 0)
            if (value != "" and value != null and integer(valueType) >= StringType.BOOLEAN and this == 0) then
                if (stringValueRecycleCount != 0) then
                    set stringValueRecycleCount = stringValueRecycleCount - 1
                    set this = stringValueRecycle[stringValueRecycleCount]
                else
                    set stringValueCount = stringValueCount + 1
                    set this = stringValueCount
                endif
                
                set stringValues[this] = value
                set stringValueTypes[this] = valueType
                set stringValueLength[this] = StringLength(value)
                set stringConvertValue[this] = convertedValue
                set stringValueId[this] = id
                call SaveInteger(stringValueIds, id, 0, this)
                
                return this
            endif
            return this
        endmethod
        
        public static method operator [] takes string s returns thistype
            return LoadInteger(stringValueIds, StringHash(s), 0)
        endmethod
        
        public method operator boolean takes nothing returns boolean
            return stringConvertValue[this] > 0
        endmethod
        
        public method operator integer takes nothing returns integer
            return stringConvertValue[this]
        endmethod
        
        public method operator integer= takes integer value returns nothing
            set stringConvertValue[this] = value
        endmethod
        
        public method operator string takes nothing returns string
            return stringValues[this]
        endmethod
        
        public method operator type takes nothing returns StringType
            return stringValueTypes[this]
        endmethod
    endstruct
    
    private module Initializer
        private static method onInit takes nothing returns nothing
            set stringTypeCount = BOOLEAN
            
            set stringTypeNames[NULL] = "null"
            set stringTypeNames[BOOLEAN] = "boolean"
            set stringTypeNames[ASCII] = "ascii"
            set stringTypeNames[INTEGER] = "integer"
            set stringTypeNames[REAL] = "real"
            set stringTypeNames[STRING] = "string"
            set stringTypeNames[STACK] = "stack"
            
            set stringValueCount = 4
            set stringValues[1] = "true"
            set stringValues[2] = "false"
            set stringValues[3] = "on"
            set stringValues[4] = "off"
            call SaveInteger(stringValueIds, StringHash("true"), 0, 1)
            call SaveInteger(stringValueIds, StringHash("false"), 0, 2)
            call SaveInteger(stringValueIds, StringHash("on"), 0, 3)
            call SaveInteger(stringValueIds, StringHash("off"), 0, 4)
            set stringValueTypes[1] = BOOLEAN
            set stringValueTypes[2] = BOOLEAN
            set stringValueTypes[3] = BOOLEAN
            set stringValueTypes[4] = BOOLEAN
            set stringValueLength[1] = 4
            set stringValueLength[2] = 5
            set stringValueLength[3] = 2
            set stringValueLength[4] = 3
            set stringConvertValue[1] = 1
            set stringConvertValue[2] = 0
            set stringConvertValue[3] = 1
            set stringConvertValue[4] = 0
            
            set stringTypeExtend[INTEGER] = REAL
            call SaveBoolean(stringValueIds, INTEGER, REAL, true)
        endmethod
    endmodule

    struct StringType extends array
        public static constant integer NULL = 0
        public static constant integer ASCII = 1
        public static constant integer INTEGER = 2
        public static constant integer REAL = 3
        public static constant integer STACK = 4
        public static constant integer STRING = 5
        public static constant integer BOOLEAN = 6
        
        public static method create takes string name, StringType extend returns thistype
            if (integer(extend) >= BOOLEAN or integer(extend) == 0) then
                set stringTypeCount = stringTypeCount + 1
                set stringTypeNames[stringTypeCount] = name
                set stringTypeExtend[stringTypeCount] = extend
                loop
                    exitwhen integer(extend) == 0
                    call SaveBoolean(stringValueIds, stringTypeCount, extend, true)
                    set extend = stringTypeExtend[extend]
                endloop
                return stringTypeCount
            endif
            return 0
        endmethod
        
        public method operator name takes nothing returns string
            return stringTypeNames[this]
        endmethod
        
        public method operator extends takes nothing returns StringType
            return stringTypeExtend[this]
        endmethod
        
        public method is takes StringType ofType returns boolean
            return this == ofType or (this != NULL and (ofType == STRING and this != STACK) or LoadBoolean(stringValueIds, this, ofType))
        endmethod
        
        implement Initializer
    endstruct
endlibrary


I'm sure wc3c had a bunch as well.

Main things are:
1) Syntax. Public functions are not user-friendly. Struct static methods are preferable for stuff like this.

2) Existing functionality. Have a look at the other systems. Is yours adding new stuff that others do not? If so, highlight that please. Let people know why yours is distinct. Maybe this could be called StringSearchUtils or something along those lines.

3) JPAG principles - JASS globals are CapitalCased while variables are camelCased. Structs can go either way, but commonly tend towards StructName.methodName syntax
 
Last edited:

B22

B22

Level 6
Joined
Sep 29, 2014
Messages
49
Thanks for the review Bribe, I was too sleepy to write a description when I first posted this. Should be updated now, hopefully it satisfies. Also added another function.

To answer some of your questions:
  • Yes, I have seen some of the existing String Systems.
    • But not the one by Nestharus you just quoted on, never stumbled on the link while doing research on hive. But I guess it should be expected that he has done everything.
      • Haven't read every single thing since it's so long, but it does appear to be really powerful, as expected of Nestharus. It also contained the string type checker which I was still pondering how to implement.
      • I also liked the idea of treating it like stacks, might use this as my 2D arrays from now on. Although, since I am mostly trying to power PRAT with this library, having a copious amount of bracket might not be too readable for editors.
      • I would like to note also, that the String Parser apparently predetermined the "separator" as well as "stack" definition, which I am trying to avoid when making this library.
    • I have also apparently missed the Explode String. Maybe because I still don't know what is supposed to be the proper name for separating strings into an array, thus missing the google search.
      • If I have to comment on this one, is that it ignores empty strings which is a feature I do not want. There are cases where I want to have empty strings.
    • It is of course worth noting that with some modifications to the codes you mentioned to fulfill the issues above, I will not doubt that it is capable of a better performance. It also gave me some ideas to improve on this library. So, thank you very much for guiding me there.
  • Regarding structs...
    • As I am working more with GUI, I am only treating JASS as a support library. Creating a new struct that while can be more efficient in JASS codes, will make it more difficult if I want to refer the variable inside a GUI line, which is why I am sticking to the native string.
      • It is also the reason why I skipped Bannar's C++ String implementation.
      • The struct used in Explode String however, actually seems non-intrusive to my goal. I will see if I can start implementing such structs.
      • Also while I have some programming background, I am still pretty new to JASS and its integration into GUI. So, if any of the facts I stated are wrong, I am open for some new information.
    • I have also apparently missed the Explode String. Maybe because I still don't know what is supposed to be the proper name for separating strings into an array, thus missing the google search.
      • If I have to comment on this one, is that it ignores empty strings which is a feature I do not want. There are cases where I want to have empty strings.
 
It is of course worth noting that with some modifications to the codes you mentioned to fulfill the issues above, I will not doubt that it is capable of a better performance.

I can assure you that it isn't to do with performance. Structs are a way of grouping similar data.

A library is something comparable to a module in languages such as Lua/TypeScript.

A struct that extends array and only uses static methods is like a class with only static methods in other languages.

Basically instead of moStrLib_strSearch:

StrLib.search

It is just as easy to type that in custom script as it is with the underscore. You wouldn't add any work aside from refactoring any existing project - which is actually technically unnecessary since for custom maps the libraries tend to be home brew anyway.

The syntax is just:

JASS:
struct StrLib extends array
    static method search takes typeA param1, typeB param2 returns type3
        //...
    endmethod
endstruct

"cleanAdjacentSpaces" could probably be renamed to "trim".

You may want to consider what "split" is doing and what "indexer" is doing, as they seem to be to be doing similar jobs. "Indexer" doesn't make sense as a keyword in this context. "findIndices" or "findIndexes" would perhaps work better.

What are your thoughts towards returning an array of values for splitting a string based on a character (explodeString/splitString in other languages)? The array could even be a udg_ global variable that integrates with this library.
 

B22

B22

Level 6
Joined
Sep 29, 2014
Messages
49
What are your thoughts towards returning an array of values for splitting a string based on a character (explodeString/splitString in other languages)? The array could even be a udg_ global variable that integrates with this library.
This was actually my first thought when I was working on this. But from what I've learned, JASS cannot take or return arrays or any multiple data structure variable, unless perhaps we define it ourselves. Ideally, I would return an array of strings for the "Indexer" and maybe a pair<string, string> for splitter if I'm working outside of WC3. This would be more efficient as every index will be stored instead of looping everything all over again.
As for your suggestion regarding the usage of global variable, I assume you are referring to a global variable created specifically for the library to use. I wasn't sure that pairing global variable with libraries are considered ok, so I was avoiding that on purpose. Definitely will be a better implementation, in my opinion.

You may want to consider what "split" is doing and what "indexer" is doing, as they seem to be to be doing similar jobs.
Now that you mentioned this, I do think they look familiar in a quick skim. But I guess, it would be similar to why I have functions like replaceAll and removeAll which are just calling other functions with set parameters to simplify its use.
In this case, for "indexer" to do what "split" is doing, we will have to append every single string that have been separated on both sides. For "split" to do what "indexer" is doing, it will have to perform some sort of a loop, maybe a recursive would be nice here. I guess, it would be fine to implement the latter to "indexer" so that it will call "split" in loops instead of being a full function itself.
"cleanAdjacentSpaces" could probably be renamed to "trim".
"Indexer" doesn't make sense as a keyword in this context. "findIndices" or "findIndexes" would perhaps work better.
I am open for naming suggestions, never had confidence in doing it. 😅


Thank you for your explanation of what struct is doing, I will read more about it and see how it can improve this.
 
One thing worth mentioning is that, for vJass users, you can return a Table with the entries. A Table can store any number of strings, and with Table version 6 you can also iterate the entries in the Table. So it would be easy to have a Table with any number of elements inside of it, and have it be returned by a function.

To access that same Table from GUI, you would have to copy its contents in vJass to a udg_ array that GUI can access. But I do plan to give GUI some access to the vJass Table's hashtable, but it is mainly intended for use with custom trigger data files such as those built using GUIGUI.
 
Top