1. Triumph has risen from these uncharted shores. The 34th Modeling Contest Results are out!
    Dismiss Notice
  2. Awaken what lies in the heart of your swarm. The 17th Techtree Contest has arrived!
    Dismiss Notice
  3. The Hive Workshop is launching its first HD modelling contest. How HD should it be?
    Dismiss Notice
  4. Check out the Staff Job Openings thread.
    Dismiss Notice
Dismiss Notice
Hive 3 Remoosed BETA - NOW LIVE. Go check it out at BETA Hive Workshop! Post your feedback in this new forum BETA Feedback.
Dismiss Notice
60,000 passwords have been reset on July 8, 2019. If you cannot login, read this.

[Extension] Math Parser

Discussion in 'JASS Resources' started by looking_for_help, May 16, 2013.

  1. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    This is a library that allows dynamic evaluation of mathematical string expressions, making it possible to implement an ingame calculator to the game. This is an Extension to the Math library.

    Code (vJASS):

    library MathParser /* v 1.1.0.0
    **********************************************************************************
    *
    *   MathParser
    *   ¯¯¯¯¯¯¯¯¯¯
    *   By looking_for_help aka eey
    *
    *   This system provides methods for parsing mathematically string expressions,
    *   represented as strings. Also methods for formating string expressions are
    *   provided, making it possible to implement an ingame calculator to the game.
    *
    ***********************************************************************************
    *
    *   Requirements
    *   ¯¯¯¯¯¯¯¯¯¯¯¯
    *   */
     uses Maths   /*  hiveworkshop.com/forums/spells-569/advanced-maths-ingame-calculator-234024/?prev=r%3D20%26page%3D5
    *
    ***********************************************************************************
    *
    *   Implementation
    *   ¯¯¯¯¯¯¯¯¯¯¯¯¯¯
    *   To use this system, you need the library Math. Once you implemented it, just
    *   copy this script to your trigger editor, then you can use it straight away. To
    *   see how the evaluation function works, compare the example IngameCalculator
    *   trigger for an example usage.
    *
    ***********************************************************************************
    *
    *   System API
    *   ¯¯¯¯¯¯¯¯¯¯
    *   readonly static real ans
    *       - Stores the last result of the evaluation method. Initialized as 0.
    *
    *   static method calculate takes string expression returns real
    *       - Use this method to calculate a mathematic expression presented as
    *         a string. Allowed values are numbers, the decimal seperator, basic
    *         arithmetic operators like +, -, *, / and ^ as well as parenthesis.
    *         You can also put "ans" into the expression to refer to the last
    *         calculation result. Error messages on math and syntax errors will
    *         be displayed if specified so in the configurable globals. The
    *         syntax parsing strictly follows Matlab conventions.
    *
    *   static method formatExpression takes string expression returns string
    *       - Use this method to format an user defined expression so that it
    *         looks nice. Compare the IngameCalculator trigger for an example
    *         usage.
    *
    *********************************************************************************/


        globals
            /*************************************************************************
            *   Customizable globals
            *************************************************************************/

           
            // Do you want the system to display error messages on math errors?
            private constant boolean DISPLAY_MATH_ERRORS = true
           
            // Do you want the system to display error messages on syntax errors?
            private constant boolean DISPLAY_SYNTAX_ERRORS = true
               
            /*************************************************************************
            *   End of customizable globals
            *************************************************************************/

        endglobals

        private module Init
            private static method onInit takes nothing returns nothing
                call init()
            endmethod
        endmodule
       
        struct MathParser extends array
            readonly static real ans = 0.0
            private static constant integer ADDITION = 1
            private static constant integer SUBSTRACTION = 2
            private static constant integer MULTIPLICATION = 3
            private static constant integer DIVISION = 4
            private static constant integer EXPONENTIATION = 5
           
            private static method getPriority takes string op returns integer
                return LoadInteger(Maths_h, StringHash(op), 0)
            endmethod
       
            private static method error takes string s, integer flag returns nothing
                local real crashThread
                if flag == 0 then
                    static if DISPLAY_MATH_ERRORS then
                        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 60.0, "|cffff0000Math Error!|r "+s)
                    endif
                else
                    static if DISPLAY_SYNTAX_ERRORS then
                        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 60.0, "|cffff0000Syntax Error!|r "+s)
                    endif
                endif
                set crashThread = 0/0
            endmethod
       
            private static method convertExpression takes string expression returns string
                local integer stringLen = StringLength(expression)
                local integer i = 0
                local integer stackCounter = -1
                local string postfix = ""
                local string array stack
                local string actualChar
                local string prevChar
                local boolean decimalDetected = false
                local boolean numberDetected = false
                local boolean numberOnceDetected = false
                local boolean unaryOperator = false
                local integer openBraces = 0

                loop
                    exitwhen i > stringLen
                    set prevChar = SubString(expression, i - 1, i)
                    set actualChar = SubString(expression, i, i + 1)
                    set unaryOperator = not (prevChar == ")" or Math.isDigit(prevChar))

                    if actualChar == ")" and openBraces < 1 then
                        call thistype.error("Unbalanced parenthesis!", 1)
                    endif
                   
                    if actualChar == "m" or actualChar == "p" or actualChar == "M" or actualChar == "P" then
                        call thistype.error("Undefined symbols used!", 1)
                    endif
                    if actualChar == "+" and unaryOperator and prevChar != "^"  then
                        set actualChar = "p"
                    elseif actualChar == "-" and unaryOperator and prevChar != "^" then
                        set actualChar = "m"
                    elseif actualChar == "-" and unaryOperator and prevChar == "^" then
                        set actualChar = "M"
                    elseif actualChar == "+" and unaryOperator and prevChar == "^" then
                        set actualChar = "P"
                    endif
                    if Math.isDigit(actualChar) then
                        set postfix = postfix+actualChar
                        set numberDetected = true
                        set numberOnceDetected = true
                    elseif actualChar == "." then
                        if decimalDetected == false and Math.isDigit(SubString(expression, i - 1, i)) and Math.isDigit(SubString(expression, i + 1, i + 2)) then
                           set postfix = postfix+actualChar
                           set decimalDetected = true
                        else
                            call thistype.error("Incorrect use of decimal point!", 1)
                        endif
                    elseif thistype.isOperator(actualChar) or actualChar == "(" or actualChar == ")" then
                        set decimalDetected = false
                        if numberDetected then
                            set numberDetected = false
                            set postfix = postfix+" "
                        endif

                        if thistype.getPriority(actualChar) > thistype.getPriority(stack[stackCounter]) or actualChar == "("  then
                            set stackCounter = stackCounter + 1
                            set stack[stackCounter] = actualChar
                            if actualChar == "(" then
                                set openBraces = openBraces + 1
                            endif
                        elseif openBraces > 0 and actualChar != ")" and actualChar != "("  and thistype.getPriority(actualChar) <= thistype.getPriority(stack[stackCounter]) then
                            loop
                                exitwhen stack[stackCounter] == "("
                                set postfix = postfix+stack[stackCounter]+" "
                                set stackCounter = stackCounter - 1
                            endloop
                            set stackCounter = stackCounter + 1
                            set stack[stackCounter] = actualChar
                        else
                            if actualChar == ")" then
                                set openBraces = openBraces - 1
                                loop
                                    exitwhen stack[stackCounter] == "("
                                    set postfix = postfix+stack[stackCounter]+" "
                                    set stackCounter = stackCounter - 1
                                endloop

                                set stack[stackCounter] = ""
                                set stackCounter = stackCounter - 1
                            else
                                loop
                                    exitwhen stackCounter < 0
                                    set postfix = postfix+stack[stackCounter]+" "
                                    set stackCounter = stackCounter - 1
                                endloop
                                set stackCounter = stackCounter + 1
                                set stack[stackCounter] = actualChar
                            endif
                        endif
                    else
                        if i != stringLen then
                            call thistype.error("Undefined symbols used!", 1)
                        endif
                    endif
                    set i = i + 1
                endloop

                if not numberOnceDetected then
                    call thistype.error("Invalid expression!", 1)
                endif
                if stackCounter >= 0 then
                    if openBraces > 0 then
                        call thistype.error("Unbalanced parenthesis!", 1)
                    endif
                    loop
                        exitwhen stackCounter < 0
                        if stack[stackCounter] != "(" then
                            if numberDetected then
                                set postfix = postfix+" "+stack[stackCounter]+" "
                                set numberDetected = false
                            else
                                set postfix = postfix+stack[stackCounter]+" "
                            endif
                        endif
                        set  stackCounter = stackCounter - 1
                    endloop
                endif
                set stringLen = StringLength(postfix)
                if Math.isDigit(SubString(postfix, stringLen - 1, stringLen)) then
                    set postfix = postfix+" "
                endif

                return postfix
            endmethod
           
            private static method subCalc takes string op1, string op2, string op returns real
                local real r1 = S2R(op1)
                local real r2 = S2R(op2)
                local integer localOp
               
                if op1 == null or op2 == null then
                    call thistype.error("Unbalanced operators!", 1)
                endif
                set localOp = LoadInteger(Maths_h, StringHash(op), 1)
                if localOp < MULTIPLICATION then
                    if localOp == ADDITION then
                        return r1 + r2
                    else
                        return r1 - r2
                    endif
                else
                    if localOp == MULTIPLICATION then
                        return r1*r2
                    elseif localOp == DIVISION then
                        if r2 == 0 then
                            call thistype.error("Division by zero!", 0)
                        endif
                        return r1/r2
                    else
                        if r1 < 0.0 and not Math.isInteger(r2) then
                            call thistype.error("(N-th) square root from negativ value not defined!", 0)
                        endif
                        return Pow(r1, r2)
                    endif
                endif
                call thistype.error("Undefined operators!", 1)
                return 0.0
            endmethod
           
            private static method isOperator takes string s returns boolean
                return s == "+" or s == "-" or s == "*" or s == "/" or s == "^" or s == "(" or s == "p" or s == "m" or s == "P" or s == "M"
            endmethod
           
            private static method prepareExpression takes string expression returns string
                local integer stringLen = StringLength(expression)
                local integer i = 0
                local string actualChar
                local string prevChar
                local string nextChar
               
                loop
                    exitwhen i > stringLen
                    set actualChar = SubString(expression, i, i + 1)
                    if actualChar == " " then
                        set expression = SubString(expression, 0, i)+SubString(expression, i + 1, stringLen)
                        set i = i - 1
                    endif
                    set i = i + 1
                endloop
               
                set i = 0
                set stringLen = StringLength(expression)
                loop
                    exitwhen i > stringLen
                    if SubString(expression, i, i + 3) == "ans" then
                        set expression = SubString(expression, 0, i)+"("+R2S(ans)+")"+SubString(expression, i + 3, stringLen)
                        set stringLen = StringLength(expression)
                    endif
                   
                    set prevChar = SubString(expression, i - 1, i)
                    set actualChar = SubString(expression, i, i + 1)
                    set nextChar = SubString(expression, i + 1, i + 2)
                   
                    if actualChar == "+" then
                        if SubString(expression, i + 1, i + 2) == "+" then
                            if Math.isDigit(SubString(expression, i - 1, i)) then
                                set expression = SubString(expression, 0, i)+"+"+SubString(expression, i + 2, stringLen)
                            else
                                set expression = SubString(expression, 0, i)+SubString(expression, i + 2, stringLen)
                            endif
                            set i = i - 1
                        elseif SubString(expression, i + 1, i + 2) == "-" then
                            set expression = SubString(expression, 0, i)+"-"+SubString(expression, i + 2, stringLen)
                            set i = i - 1
                        endif
                    elseif actualChar == "-" then
                        if SubString(expression, i + 1, i + 2) == "+" then
                            set expression = SubString(expression, 0, i)+"-"+SubString(expression, i + 2, stringLen)
                            set i = i - 1
                        elseif SubString(expression, i + 1, i + 2) == "-" then
                            if Math.isDigit(SubString(expression, i - 1, i)) then
                                set expression = SubString(expression, 0, i)+"+"+SubString(expression, i + 2, stringLen)
                            else
                                set expression = SubString(expression, 0, i)+SubString(expression, i + 2, stringLen)
                            endif
                            set i = i - 1
                        endif
                    elseif actualChar == "(" and (Math.isDigit(prevChar) or prevChar == ")") then
                        set expression = SubString(expression, 0, i)+"*"+SubString(expression, i, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    elseif actualChar == ")" and Math.isDigit(nextChar) then
                        set expression = SubString(expression, 0, i + 1)+"*"+SubString(expression, i + 1, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    endif
                    set i = i + 1
                endloop
               
                return expression
            endmethod
           
            private static method evaluateExpression takes string postfix returns real
                local integer stringLen = StringLength(postfix)
                local integer i = 0
                local integer position = 0
                local integer counter = 0
                local integer stackCounter = -1
                local string actualToken
                local string array stack
                local real result = 0.0

                loop
                    exitwhen i == stringLen
                    if SubString(postfix, i, i + 1) == " " then
                        set actualToken = SubString(postfix, position, position + counter)
                        if Math.isDigit(SubString(actualToken, 0, 1)) then
                            set stackCounter = stackCounter + 1
                            set stack[stackCounter] = actualToken
                        else
                            if not (actualToken == "m " or actualToken == "p " or actualToken == "M " or actualToken == "P ") then
                                set result = thistype.subCalc(stack[stackCounter - 1], stack[stackCounter], actualToken)
                                set stack[stackCounter] = ""
                                set stack[stackCounter - 1] = R2S(result)
                                set stackCounter = stackCounter - 1
                            else
                                if actualToken == "m " or actualToken == "M " then
                                    if S2R(stack[stackCounter]) > 0 then
                                        set stack[stackCounter] = "-"+stack[stackCounter]
                                    else
                                        set stack[stackCounter] = SubString(stack[stackCounter], 1, StringLength(stack[stackCounter]))
                                    endif
                                endif
                            endif
                        endif
                        set position = i + 1
                        set counter = 0
                    endif
                    set i = i + 1
                    set counter = counter + 1
                endloop

                return S2R(stack[0])
            endmethod
       
            static method calculate takes string expression returns real
                set ans = thistype.evaluateExpression(thistype.convertExpression(thistype.prepareExpression(expression)))
                return ans
            endmethod
           
            static method formatExpression takes string expression returns string
                local integer i = 0
                local integer stringLen = StringLength(expression)
                local string prevChar
                local string nextChar
                local string actualChar
                local string prevAnsToken
                local string nextAnsToken
                local boolean unaryOperator = false
               
                loop
                    exitwhen i > stringLen
                    if SubString(expression, i, i + 1) == " " then
                        set expression = SubString(expression, 0, i)+SubString(expression, i + 1, stringLen)
                        set i = i - 1
                    endif
                    set i = i + 1
                endloop
               
                set i = 0
                set stringLen = StringLength(expression)
                loop
                    exitwhen i > stringLen
                    set actualChar = SubString(expression, i, i + 1)
                    if actualChar == "+" then
                        if SubString(expression, i + 1, i + 2) == "+" then
                            if Math.isDigit(SubString(expression, i - 1, i)) then
                                set expression = SubString(expression, 0, i)+"+"+SubString(expression, i + 2, stringLen)
                            else
                                set expression = SubString(expression, 0, i)+SubString(expression, i + 2, stringLen)
                            endif
                            set i = i - 1
                        elseif SubString(expression, i + 1, i + 2) == "-" then
                            set expression = SubString(expression, 0, i)+"-"+SubString(expression, i + 2, stringLen)
                            set i = i - 1
                        endif
                    elseif actualChar == "-" then
                        if SubString(expression, i + 1, i + 2) == "+" then
                            set expression = SubString(expression, 0, i)+"-"+SubString(expression, i + 2, stringLen)
                            set i = i - 1
                        elseif SubString(expression, i + 1, i + 2) == "-" then
                            if Math.isDigit(SubString(expression, i - 1, i)) then
                                set expression = SubString(expression, 0, i)+"+"+SubString(expression, i + 2, stringLen)
                            else
                                set expression = SubString(expression, 0, i)+SubString(expression, i + 2, stringLen)
                            endif
                            set i = i - 1
                        endif
                    endif
                    set i = i + 1
                endloop
               
                set i = 0
                set stringLen = StringLength(expression)
                loop
                    exitwhen i > stringLen
                    set actualChar = SubString(expression, i, i + 1)
                    set prevChar = SubString(expression, i - 1, i)
                    set nextChar = SubString(expression, i + 1, i + 2)
                    set prevAnsToken = SubString(expression, i - 3, i)
                    set nextAnsToken = SubString(expression, i + 1, i + 4)
                   
                    set unaryOperator = not (prevChar == ")" or Math.isDigit(prevChar) or prevAnsToken == "ans")
                    if actualChar == "+" and not unaryOperator then
                        set expression = SubString(expression, 0, i)+" + "+SubString(expression, i + 1, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i + 1
                    elseif actualChar == "+" and unaryOperator then
                        set expression = SubString(expression, 0, i)+SubString(expression, i + 1, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    elseif actualChar == "-" and not unaryOperator then
                        set expression = SubString(expression, 0, i)+" - "+SubString(expression, i + 1, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i + 1
                    elseif actualChar == "(" and (Math.isDigit(prevChar) or prevChar == ")") then
                        set expression = SubString(expression, 0, i)+"*"+SubString(expression, i, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    elseif actualChar == ")" and Math.isDigit(nextChar) then
                        set expression = SubString(expression, 0, i + 1)+"*"+SubString(expression, i + 1, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    endif
                    if prevAnsToken == "ans" and (Math.isDigit(actualChar) or actualChar == "(" or SubString(expression, i, i + 3) == "ans") then
                        set expression = SubString(expression, 0, i)+"*"+SubString(expression, i, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    elseif nextAnsToken == "ans" and (Math.isDigit(actualChar) or actualChar == ")") then
                        set expression = SubString(expression, 0, i + 1)+"*"+SubString(expression, i + 1, stringLen)
                        set stringLen = StringLength(expression)
                        set i = i - 1
                    endif
                   
                    set i = i + 1
                endloop
               
                return expression
            endmethod
           
            private static method init takes nothing returns nothing
                call SaveInteger(Maths_h, StringHash("+"), 0, 1)
                call SaveInteger(Maths_h, StringHash("-"), 0, 1)
                call SaveInteger(Maths_h, StringHash("*"), 0, 2)
                call SaveInteger(Maths_h, StringHash("/"), 0, 2)
                call SaveInteger(Maths_h, StringHash("m"), 0, 3)
                call SaveInteger(Maths_h, StringHash("p"), 0, 3)
                call SaveInteger(Maths_h, StringHash("^"), 0, 4)
                call SaveInteger(Maths_h, StringHash("M"), 0, 5)
                call SaveInteger(Maths_h, StringHash("P"), 0, 5)
                call SaveInteger(Maths_h, StringHash("("), 0, 6)
                call SaveInteger(Maths_h, StringHash("+ "), 1, ADDITION)
                call SaveInteger(Maths_h, StringHash("- "), 1, SUBSTRACTION)
                call SaveInteger(Maths_h, StringHash("* "), 1, MULTIPLICATION)
                call SaveInteger(Maths_h, StringHash("/ "), 1, DIVISION)
                call SaveInteger(Maths_h, StringHash("^ "), 1, EXPONENTIATION)
            endmethod
           
            implement Init
        endstruct
    endlibrary
     


    Example Ingame Calculator:

    Code (vJASS):

    library IngameCalculator uses MathParser
        globals
            /*************************************************************************
            *   Customizable globals
            *************************************************************************/

           
            // Define a command before performing a calculation, e.g. "-calc ".
            // Use an empty string for no command.
            private constant string commandString = ""
           
            // Type in this string to clear the screen.
            private constant string clearScreen = "clc"
           
            /*************************************************************************
            *   End of customizable globals
            *************************************************************************/

        endglobals

        private struct Calculator extends array
            readonly static string expression
       
            private static method onEnter takes nothing returns nothing
                set expression = GetEventPlayerChatString()
                if SubString(expression, 0, StringLength(commandString)) == commandString then
                    set expression = SubString(expression, StringLength(commandString), StringLength(expression))
                    if expression == clearScreen then
                        call ClearTextMessages()
                    else
                        call DisplayTimedTextToPlayer(GetLocalPlayer(), 0.0, 0.0, 60.0, MathParser.formatExpression(expression)+" = "+R2S(MathParser.calculate(expression)))
                    endif
                endif
            endmethod
           
            implement CalcInit
        endstruct
       
        module CalcInit
            private static method onInit takes nothing returns nothing
                local trigger t = CreateTrigger()
                local code c = function thistype.onEnter
                local integer i = 0

                loop
                    exitwhen i > 11
                    call TriggerRegisterPlayerChatEvent(t, Player(i), "", false)
                    set i = i + 1
                endloop
                call TriggerAddCondition(t, Filter(c))
                set t = null
                set c = null
            endmethod
        endmodule
    endlibrary
     
     

    Attached Files:

    Last edited by a moderator: Aug 16, 2013
  2. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,112
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    You can do a very specific type of parser without identifiers in a very efficient manner. The code is very, very short for the correct algorithm.

    Try again.


    You might need to do some research to figure out the correct way to do it =).

    It's recursive *hint hint*


    ofc, you can do a 2D loop instead


    Also, because this is a simple calculator without identifiers, you'll be alternating between numbers and operators or grouping symbols, so if the symbol isn't "(" or ")", then it's whatever you are currently on (number or operator). Whenever you go to a higher precedence, you push on to the stack. Whenever you go to lower precedence, you pop from the stack. The stack will just have a simple value since you have no identifiers, so it's very simple. I had to code one with n identifiers >;o, that was nasty. The result was typically an algebraic expression >.<. This is a cakewalk =).
     
  3. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    This is the correct algorithm. Its called shunting-yard algorithm and this is a common standard for something like that.
    Why should I try again? It works very good, is very efficient (its O(n), can't get better) and can do everything it should.



    I don't understand what you want to tell me.

    This library was made for a specific purpose and it fullfills this purpose. I don't know why you would want to have
    identifiers, thats not the thing this was made for.
     
    Last edited: May 17, 2013
  4. Almia

    Almia

    Joined:
    Apr 24, 2012
    Messages:
    4,760
    Resources:
    35
    Spells:
    30
    Tutorials:
    4
    JASS:
    1
    Resources:
    35
    Is this different to Nestharus' StringParser?
     
  5. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Yes, its very different. They do completly different things, maybe thats why hes mixing up some stuff here.
     
  6. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,112
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    I'm not mixing up anything... your algorithm is not that good, it can be simplified.
     
  7. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Not really... I mean the code is so "long" because the system is designed to be very robust towards the user inputs.
    If I would throw all this away, the system would be very short, but thats not the sense of making such a system.

    You (the user) can enter all of the following expressions to the calculate method:

    "2*(3+4) - (3 + 2)*8"

    or
    "2(3+4)-(3 + 2)8"

    or
    "2*(3+4) - (3 ---- 2)8"

    or
    "2*(3+4)      --- (  3   + 2) * 8"


    It will always work and it will format the input string in a way that it follows conventions and looks nice. The strings displayed above will be displayed ingame as:

    "2*(3 + 4) - (3 + 2)*8 = -26"


    Further it throws various exceptions on syntax and math errors, which is IMHO also neccessary. Not just "error!" but error-specific messages.

    The algorithm itself is perfectly fine for this task. For this specific task, parsing math expressions, its even better than a recursive solution.
     
  8. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,112
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    It doesn't need to be recursive

    edit
    here is a literal parser

    Code (vJASS):

    library LiteralParser10
        struct LiteralParser10 extends array
            readonly static real result = 0
            readonly static integer endIndex = 0
       
            static method parse takes string input, integer index returns boolean
                local integer multiplier = 1
                local integer start = index
                local string char
           
                set result = 0
                set endIndex = 0
               
                loop
                    set char = SubString(input, index, index + 1)
                    exitwhen char != "-"
                    set multiplier = multiplier*-1
                    set index = index + 1
                endloop
                set start = index
                loop
                    exitwhen (S2I(char) == 0 and char != "0")
                    set index = index + 1
                    set char = SubString(input, index, index + 1)
                endloop
               
                if (char == ".") then
                    loop
                        set index = index + 1
                        set char = SubString(input, index, index + 1)
                        exitwhen (S2I(char) == 0 and char != "0")
                    endloop
                   
                    if (char == ".") then
                        return false
                    endif
                endif
               
                set endIndex = index
                set result = multiplier*S2R(SubString(input, start, index))
               
                return true
            endmethod
        endstruct
    endlibrary
     


    Code (vJASS):

    struct Tester extends array
        private static method eval takes nothing returns boolean
            local string input = GetEventPlayerChatString()
            if (LiteralParser10.parse(input, 0)) then
                if (LiteralParser10.endIndex != StringLength(input)) then
                    call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Expression Detected")
                endif
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Value: " + R2S(LiteralParser10.result))
            else
                call DisplayTimedTextToPlayer(GetLocalPlayer(),0,0,60,"Invalid Input")
            endif
           
            return false
        endmethod
       
        private static method onInit takes nothing returns nothing
            local trigger t = CreateTrigger()
            call TriggerAddCondition(t, Condition(function thistype.eval))
            call TriggerRegisterPlayerChatEvent(t, Player(0), "", false)
            set t = null
        endmethod
    endstruct
     
     
    Last edited: May 17, 2013
  9. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Aha.

    Great. Unfortunatly it doesn't work at all for calculations, it just detects a value. Of course this is shorter than my system.
     
  10. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,112
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    No, that would be used in your thing for ripping out literals.


    There are two levels, tokenizing and parsing. You're just cramming everything into the parser, you have no tokenization.

    And here is an update to LiteralParser10 that will detect unary
    Code (vJASS):

    library LiteralParser10
        struct LiteralParser10 extends array
            readonly static real result = 0
            readonly static integer endIndex = 0
            readonly static boolean unary = false
       
            static method parse takes string input, integer index returns boolean
                local integer multiplier = 1
                local integer start = index
                local string char
           
                set result = 0
                set endIndex = 0
               
                loop
                    set char = SubString(input, index, index + 1)
                    exitwhen char != "-"
                    set multiplier = multiplier*-1
                    set index = index + 1
                endloop
                set start = index
                loop
                    exitwhen (S2I(char) == 0 and char != "0")
                    set index = index + 1
                    set char = SubString(input, index, index + 1)
                endloop
               
                if (char == ".") then
                    loop
                        set index = index + 1
                        set char = SubString(input, index, index + 1)
                        exitwhen (S2I(char) == 0 and char != "0")
                    endloop
                   
                    if (char == ".") then
                        return false
                    endif
                endif
               
                if (endIndex == index) then
                    return false
                endif
               
                set endIndex = index
                set char = SubString(input, start, index)
                if (char == "" or char == ".") then
                    set result = multiplier
                    set unary = true
                else
                    set result = multiplier*S2R(char)
                    set unary = false
                endif
               
                return true
            endmethod
        endstruct
    endlibrary
     


    You are detecting four token types: (, ), operators, and literals.

    edit
    Here are the other utils

    Code (vJASS):

    private module Init
            private static method onInit takes nothing returns nothing
                call init()
            endmethod
        endmodule
       
        private struct Operation extends array
            private static Table table
       
            static constant integer SUBTRACTION = 1
            static constant integer ADDITION = 2
            static constant integer MULTIPLICATION = 3
            static constant integer DIVISION = 4
            static constant integer EXPONENTIATION = 5
           
            readonly integer precedence
           
            static method operator [] takes string str returns thistype
                return table[StringHash(str)]
            endmethod
           
            method calculate takes real left, real right returns real
                if (integer(this) < MULTIPLICATION) then
                    if (integer(this) == SUBTRACTION) then
                        return left - right
                    else
                        return left + right
                    endif
                else
                    if (integer(this) == DIVISION) then
                        return left/right
                    elseif (integer(this) == MULTIPLICATION) then
                        return left*right
                    else
                        return Pow(left, right)
                    endif
                endif
            endmethod
           
            private static method init takes nothing returns nothing
                set table = Table.create()
               
                set table[StringHash("-")] = SUBTRACTION
                set table[StringHash("+")] = ADDITION
                set table[StringHash("*")] = MULTIPLICATION
                set table[StringHash("/")] = DIVISION
                set table[StringHash("^")] = EXPONENTIATION
               
                set thistype(SUBTRACTION).precedence = 0
                set thistype(ADDITION).precedence = 0
                set thistype(MULTIPLICATION).precedence = 1
                set thistype(DIVISION).precedence = 1
                set thistype(EXPONENTIATION).precedence = 2
            endmethod
           
            implement Init
        endstruct
       
        private struct Stack extends array
            private real value_p
            private Operation operation_p
            private boolean onValue_p
            private static thistype top = 0
            private static boolean empty_p
           
            static method operator value takes nothing returns real
                return top.value_p
            endmethod
            static method operator operation takes nothing returns Operation
                return top.operation_p
            endmethod
            static method operator onValue takes nothing returns boolean
                return top.onValue_p
            endmethod
            static method operator empty takes nothing returns boolean
                return empty_p
            endmethod
            static method operator depth takes nothing returns integer
                return top
            endmethod
           
            static method push takes real val, Operation op, boolean on, boolean empty returns nothing
                set top = top + 1
                set top.value_p = val
                set top.operation_p = op
                set top.onValue_p = on
                set top.empty_p = empty
            endmethod
            static method pop takes nothing returns nothing
                set top = top - 1
            endmethod
            static method clear takes nothing returns nothing
                set top = 0
            endmethod
        endstruct
     


    This is the if statement structure in the main loop

    Code (vJASS):

    if (char == "(") then
    elseif (char == ")") then
    elseif (onValue) then
    else
     


    Do you see how much simpler the above looks? lol

    From, here, in operations, you do your normal look ahead, so you need you'll need 2 local values and 2 local operators. Whenever reading a value, it goes into future and future goes into current. On an operator, if the future operator has higher precedence than the current, push, otherwise if the top of the stack has >= precedence than the current, pop.

    Hopefully this puts you in the right direction ; )
     
    Last edited: May 17, 2013
  11. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Not really, sorry.

    This is your proposed calculate method:

    Code (vJASS):

    method calculate takes real left, real right returns real
                if (integer(this) < MULTIPLICATION) then
                    if (integer(this) == SUBTRACTION) then
                        return left - right
                    else
                        return left + right
                    endif
                else
                    if (integer(this) == DIVISION) then
                        return left/right
                    elseif (integer(this) == MULTIPLICATION) then
                        return left*right
                    else
                        return Pow(left, right)
                    endif
                endif
            endmethod
     


    This is mine (error handling removed as yours doesn't care either):

    Code (vJASS):

    private static method subCalc takes string op1, string op2, string op returns real
                local real r1 = S2R(op1)
                local real r2 = S2R(op2)
               
                if op == "+ " then
                    return r1 + r2
                elseif op == "- " then
                    return r1 - r2
                elseif op == "* " then
                    return r1*r2
                elseif op == "/ " then
                    return r1/r2
                elseif op == "^ " then
                    return Pow(r1, r2)
                endif
                return 0.0
            endmethod
     



    Here is your precedence detection which also produces new dependencies (table) which is unneccessary and can totaly be avoided:

    Code (vJASS):

    private static method init takes nothing returns nothing
                set table = Table.create()
               
                set table[StringHash("-")] = SUBTRACTION
                set table[StringHash("+")] = ADDITION
                set table[StringHash("*")] = MULTIPLICATION
                set table[StringHash("/")] = DIVISION
                set table[StringHash("^")] = EXPONENTIATION
               
                set thistype(SUBTRACTION).precedence = 0
                set thistype(ADDITION).precedence = 0
                set thistype(MULTIPLICATION).precedence = 1
                set thistype(DIVISION).precedence = 1
                set thistype(EXPONENTIATION).precedence = 2
            endmethod
     


    Here mine

    Code (vJASS):

    private static method getPriority takes string op returns integer
                if op == "+" or op == "-" then
                    return 1
                elseif op == "*" or op == "/" then
                    return 2
                elseif op == "m" or op == "p" then
                    return 3
                elseif op == "^" then
                    return 4
                elseif op == "M" or op == "P" then
                    return 5
                elseif op == "(" then
                    return 6
                endif
                return 0
            endmethod
     



    Bugs for unary plus.

    Of course I have a tokenisation lol.^^


    To the rest: The thing that needs space is the postfix conversion which is not included to your stuff, so its obvious yours looks smaller.

    Basically I don't see the big improvements here, I think this is more about your subjective preferences rather than real improvements.
     
  12. Nestharus

    Nestharus

    Joined:
    Jul 10, 2007
    Messages:
    6,112
    Resources:
    8
    Spells:
    3
    Tutorials:
    4
    JASS:
    1
    Resources:
    8
    My calc one is more efficient >.< same with the the priority.

    I only put in -. I've never actually seen anyone put in +6, lol.
     
  13. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Kk. I tested it and its a little bit more efficient (about 8%). Although its not really worth the effort imho, I updated both the priority and calculate mathod. Btw, is there a way I could access the private hashtable from the Math library? I made it public now because I don't want to make a new one just for those few values...

    Always expect the worst case.
     
  14. Magtheridon96

    Magtheridon96

    Joined:
    Dec 12, 2008
    Messages:
    5,542
    Resources:
    26
    Maps:
    1
    Spells:
    8
    Tutorials:
    7
    JASS:
    10
    Resources:
    26
    I don't know if "String Parser" would be the correct name for this resource.

    I'm tempted to change it, but I'm not sure what it should be changed to exactly.

    Suggestions?

    (Also, if you can actually shorten the function by dividing it into many child functions and structs and modules, it would be really awesome! (Though this is not a requirement. I'd approve this in its current state)
     
  15. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Yes, thats right, StringParser is probably not the perfect name for this. It may suggest functionality thats not provided.

    I would suggest "MathInterpreter", as thats basically what its doing; interpreting math expressions.
     
  16. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Update to Version 1.1.0.0
    - Changed the name to MathParser
    - Not any longer an optional module to Math, but an own library that requires Math
     
  17. TriggerHappy

    TriggerHappy

    Code Moderator

    Joined:
    Jun 23, 2007
    Messages:
    2,880
    Resources:
    22
    Spells:
    11
    Tutorials:
    2
    JASS:
    9
    Resources:
    22
    Approved.
     
  18. looking_for_help

    looking_for_help

    Joined:
    Dec 12, 2012
    Messages:
    993
    Resources:
    5
    Spells:
    2
    JASS:
    3
    Resources:
    5
    Cool, just realized right now. Thanks^^