• Listen to a special audio message from Bill Roper to the Hive Workshop community (Bill is a former Vice President of Blizzard Entertainment, Producer, Designer, Musician, Voice Actor) 🔗Click here to hear his message!
  • Read Evilhog's interview with Gregory Alper, the original composer of the music for WarCraft: Orcs & Humans 🔗Click here to read the full interview.

[Extension] Math Parser

Level 14
Joined
Dec 12, 2012
Messages
1,007
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.

JASS:
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:

JASS:
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
 

Attachments

  • MathParser.w3x
    60.5 KB · Views: 204
Last edited by a moderator:
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 =).
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
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*

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.



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 =).

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:
Level 14
Joined
Dec 12, 2012
Messages
1,007
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.
 
It doesn't need to be recursive

edit
here is a literal parser

JASS:
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

JASS:
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:
Level 14
Joined
Dec 12, 2012
Messages
1,007
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
JASS:
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

JASS:
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

JASS:
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:
Level 14
Joined
Dec 12, 2012
Messages
1,007
Do you see how much simpler the above looks? lol

Not really, sorry.

This is your proposed calculate method:

JASS:
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):

JASS:
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:

JASS:
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

JASS:
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


And here is an update to LiteralParser10 that will detect unary

Bugs for unary plus.

You're just cramming everything into the parser, you have no tokenization.

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.
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
My calc one is more efficient >.< same with the the priority.

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...

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

Always expect the worst case.
 
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)
 
Level 14
Joined
Dec 12, 2012
Messages
1,007
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.

Yes, thats right, StringParser is probably not the perfect name for this. It may suggest functionality thats not provided.

Suggestions?

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