SemlerPDX icon

AVCS Voice Calculator & Unit Conversions Inline Function in VB.net for VoiceAttack

SemlerPDX | PRO | 01/17/21 09:13:23 PM UTC (Edited) | 0 ⭐ | 896 👁️ | Never ⏰ | []
VB.NET |

30.16 KB

|

None

|

0 👍

/

0 👎

'AVCS Voice Calculator & Conversions (v1.0) -- Calculate expressions with up to 2 operations and 3 values "Any way you say it"
'by SemlerPDX Mar2020/Jan2021
'VETERANS-GAMING.COM
 
'When I say: *[divided by;divide by;divided that;divide that;divided;divide;multiply;times;multiplied by;multiply that;multiplied that;times that;add;add that;added that;plus;subtract;subtracted;minus;% of;to the power of;percent of;swear;square;square root of;square of;squared;cubed;cube;cuban;° c;° f;kelvin;celcius;celsius;centigrade;fahrenheit;kelvin;calvin;cm;centimeters;centimetres;inches;foot;feet;meters;metres;yards;yours;miles;km;kilometers;kilometres;feet per second;mph;miles per hour;kph;kilometers per hour;kilometres per hour;knots;mach;mock;mark]*
'Set text [~avcs_cmd] to '{TXTWORDTONUM:"{CMD}"}' (Trim) (Lower Case)
 
Imports Microsoft.VisualBasic
Imports System
Imports System.Math
Imports System.Globalization
 
Public Class VAInline
    dim decimalSeparator as string = CultureInfo.CurrentCulture.NumberFormat.NumberDecimalSeparator
    dim decimalPlaces as Int32 = 3   'Default = 3   |  rounds results -- change via voice command 'Set Decimal Places [to;] [0..16]'
    dim countHistory as integer = 1
    
    'List Comparison Vars - must start and end with whitespace to check for whole word due to wildcard recognition
    dim OperatorsList as string = " divided divide times multiplied multiply add plus subtract subtracted minus - % percent power square squared squareroot cubed cubes cube cuban cuba "
    dim ConvertorsList as string = " c f k cm celsius celcius centigrade fahrenheit kelvin calvin inches centimeters centimetres foot feet m meters metres yards yours miles km kilometers kilometres fps feetpersecond mps meterspersecond metrespersecond mph milesperhour kph kilometersperhour kilometresperhour knots mach mark mock "
    dim OpsList as string = " per dif prd sum sub exp sqr "
    dim DoubleOpsList as string = " dif prd sum sub "
    dim SymbolsList as string = " ° # $ % ^ & * - + = : / "
    dim PiList as string = " pi pie tie high bye eye espy pine pied ply tie "
    'Speech Parsing Vars
    dim cmd as string = ""
    dim charArray() as char
    dim cmdSegments() as string
    'Values Decimal Vars
    dim operand1 as decimal?
    dim operand2 as decimal?
    dim operand3 as decimal?
    dim operandLast as decimal?
    dim result as decimal?
    dim resultLast as decimal?
    'Operations String Vars
    dim operator1 as string
    dim operator2 as string
    dim operatorText1 as string = ""
    dim operatorText2 as string = ""
    dim equationText as string = ""
    
    dim operationsOrder as integer = 0
    dim faultCount as integer = 0
    dim randomText as string
    dim subReverse as boolean
    dim calcActive as boolean
    
    
    'Function to fix large number separators by culture
    Private Function FixLargeNumberSeparator(ByRef cmd as string)
        'ex. (7,000.32 into 7000.32)  [or]  (7.000,32 into 7000,32)
        if ((cmd.Contains(".")) or (cmd.Contains(",")))
            if (decimalSeparator <> ".")
                if (cmd.Contains("."))
                    cmd = cmd.Replace(".", "")
                end if
            elseif (decimalSeparator <> ",")
                if (cmd.Contains(","))
                    cmd = cmd.Replace(",", "")
                end if
            end if
        end if  
    end function
    
    
    'Function to transform large number words and concat with the number said just before
    Private Function FixLargeWordNum(ByRef largeType as string, ByRef lastNum as decimal?)
        select case largeType
            case "million"
                lastNum *= 1000000
            case "billion"
                lastNum *= 1000000000
            case "trillion"
                lastNum *= 1000000000000
            case "quadrillion"
                lastNum *= 1000000000000000
            case else
                largeType = "AVCS Calculation Error - unable to resolve '" + lastNum.ToString() + " " + largeType + "' as an opperand"
        end select
    end function
    
    
    'Function to test for "by" as Pi in specific cases
    Private Function HasPi(ByVal operatorName as string)
        if (not((operatorName.Contains("root")) or (operatorName.StartsWith("square")) or (operatorName.StartsWith("cub"))))
            cmd = cmd.Replace("   "," ").Replace("  "," ").Replace("divided by","").Replace("divide by","").Replace("multiplied by","").Replace("multiply by","").Replace("that by","")
            if ((cmd.StartsWith("by ")) or (cmd.Contains(" by ")) or (cmd.EndsWith(" by")))
                Return true
            end if
        end if
    end function
    
    
    'Function to set keyword and readable text/symbol for Operators
    Private Function SetOperator(ByRef operatorName as string, ByRef operatorText as string)
        if ((operatorName.Contains("%")) or (operatorName.Contains("percent")))
            operatorText = "% of"
            operatorName = "per"
        elseif (operatorName.StartsWith("divide"))
            operatorText = "/"
            operatorName = "dif"
        elseif ((operatorName.Contains("times")) or (operatorName.StartsWith("multipl")))
            operatorText = "x"
            operatorName = "prd"
        elseif ((operatorName.Contains("plus")) or (operatorName.StartsWith("add")))
            operatorText = "+"
            operatorName = "sum"
        elseif ((operatorName.StartsWith("subtract")) or (operatorName.Contains("minus")) or (operatorName.Contains("-")))
            operatorText = "-"
            operatorName = "sub"
        elseif (operatorName.Contains("power"))
            operatorText = "to the power of"
            operatorName = "exp"
        elseif (operatorName.Contains("squareroot"))
            operatorText = "the square root of"
            operatorName = "sqr"
        elseif (operatorName.StartsWith("square"))
            operatorText = "squared"
            operatorName = "exp"
        elseif (operatorName.StartsWith("cub"))
            operatorText = "cubed"
            operatorName = "exp"
        end if
    end function
    
    
    'Function to set keyword and readable text/symbol for Conversions
    Private Function SetConvertor(ByRef operatorName as string, ByRef operatorText as string)
        'VELOCITY CONVERSIONS==============
        if ((operatorName.Contains("fps")) or (operatorName.Contains("feetpersecond")))
            operatorText = "feet per second"
            operatorName = "fps2"
        elseif ((operatorName.Contains("mps")) or (operatorName.Contains("meterspersecond")) or (operatorName.Contains("metrespersecond")))
            operatorText = "meters per second"
            operatorName = "mps2"
        elseif ((operatorName.Contains("mph")) or (operatorName.Contains("milesperhour")))
            operatorText = "miles per hour"
            operatorName = "mph2"
        elseif ((operatorName.Contains("kph")) or (operatorName.Contains("kilometersperhour")) or (operatorName.Contains("kilometresperhour")))
            operatorText = "kilometers per hour"
            operatorName = "kph2"
        elseif (operatorName.Contains("knots"))
            operatorText = "knots"
            operatorName = "kts2"
        elseif ((operatorName.Contains("mach")) or (operatorName.Contains("mock")) or (operatorName.Contains("mark")))
            operatorText = "mach"
            operatorName = "mch2"
        'DISTANCE CONVERSIONS==============
        elseif (operatorName.Contains("inches"))
            operatorText = "inches"
            operatorName = "in2"
        elseif ((operatorName.Contains("cm")) or (operatorName.Contains("centimeters")) or (operatorName.Contains("centimetres")))
            operatorText = "centimeters"
            operatorName = "cm2"
        elseif ((operatorName.Contains("feet")) or (operatorName.Contains("foot")))
            operatorText = "feet"
            operatorName = "ft2"
        elseif ((operatorName.Contains("yards")) or (operatorName.Contains("yours")))
            operatorText = "yards"
            operatorName = "yd2"
        elseif ((operatorName.Contains("km")) or (operatorName.Contains("kilometers")) or (operatorName.Contains("kilometres")))
            operatorText = "kilometers"
            operatorName = "km2"
        elseif ((operatorName.Contains("mi")) or (operatorName.Contains("miles")))
            operatorText = "miles"
            operatorName = "mi2"
        elseif ((operatorName.Contains("meters")) or (operatorName.Contains("metres")))
            operatorText = "meters"
            operatorName = "mt2"
        'TEMPERATURE CONVERSIONS==============
        elseif (operatorName.Contains("fahrenheit"))
            operatorText = "degrees fahrenheit"
            operatorName = "f2"
        elseif ((operatorName.Contains("celcius")) or (operatorName.Contains("celsius")))
            operatorText = "degrees celcius"
            operatorName = "c2"
        elseif (operatorName.Contains("centigrade"))
            operatorText = "degrees centigrade"
            operatorName = "c2"
        elseif ((operatorName.Contains("kelvin")) or (operatorName.Contains("calvin")))
            operatorText = "kelvin"
            operatorName = "k2"
        end if
    end function
    
    
    'Main Maths Function
    Private Function GetResult(ByVal ops as string, ByVal val1 as decimal?, Optional ByVal val2 as decimal? = 0)
        select case ops
            case "sum"
                val1 += val2
            case "sub"
                val1 -= val2
            case "prd"
                val1 *=  val2
            case "dif"
                val1 /= val2
            case "per"
                val1 *= (operand2 / 100)
            case "exp"
                val1 = Math.Pow(val1, val2)
            case "sqr"
                val1 = Math.Sqrt(val1)
            case else
                val1 = nothing
        end select
        Return val1
    end function
    
    
    'Main Conversions Function
    Private Function GetConversion(ByVal ops as string, ByVal val as decimal?)
        select case ops
        '==DISTANCE===========
            'inches
            case "in2cm"
                val *= 0.393701
            case "in2ft"
                val *= 0.0833333
            case "in2mt"
                val *= 0.0254
            case "in2yd"
                val *= 0.0277778
            case "in2km"
                val /= 39370
            case "in2mi"
                val /= 63360
            'cm
            case "cm2in"
                val *= 0.393701
            case "cm2ft"
                val *= 0.0328084
            case "cm2mt"
                val /= 100
            case "cm2yd"
                val *= 0.0109361
            case "cm2km"
                val /= 100000
            case "cm2mi"
                val /= 160934
            'ft
            case "ft2in"
                val *= 12
            case "ft2cm"
                val *= 30.48
            case "ft2mt"
                val *= 0.3048
            case "ft2yd"
                val *= 0.333333
            case "ft2km"
                val /= 3280.84
            case "ft2mi"
                val /= 5280
            'mt
            case "mt2in"
                val *= 39.3701
            case "mt2cm"
                val *= 100
            case "mt2ft"
                val *= 3.28084
            case "mt2yd"
                val *= 1.09361
            case "mt2km"
                val /= 1000
            case "mt2mi"
                val /= 1609
            'yd
            case "yd2in"
                val *= 36
            case "yd2cm"
                val *= 91.44
            case "yd2ft"
                val *= 3
            case "yd2mt"
                val *= 0.9144
            case "yd2km"
                val /= 1093.61
            case "yd2mi"
                val /= 1760
            'km
            case "km2in"
                val *= 39370.1
            case "km2cm"
                val *= 100000
            case "km2ft"
                val *= 3280.84
            case "km2mt"
                val *= 1000
            case "km2yd"
                val *= 1093.61
            case "km2mi"
                val *= 0.621371
            'mi
            case "mi2in"
                val *= 63360
            case "mi2cm"
                val *= 160934.4
            case "mi2ft"
                val *= 5280
            case "mi2mt"
                val *= 1609.34
            case "mi2yd"
                val *= 1760
            case "mi2km"
                val *= 1.60934
        '==VELOCITY===========
            'fps
            case "fps2mps"
                val *= 0.3048
            case "fps2kph"
                val *= 1.09728
            case "fps2mph"
                val *= 0.681818
            case "fps2kts"
                val *= 0.592484
            case "fps2mch"
                val *= 0.00088863
            'mps
            case "mps2fps"
                val *= 3.28084
            case "mps2kph"
                val *= 3.6
            case "mps2mph"
                val *= 2.23694
            case "mps2kts"
                val *= 1.94384
            case "mps2mch"
                val *= 0.00291545
            'kph
            case "kph2fps"
                val *= 0.911344
            case "kph2mps"
                val *= 0.277778
            case "kph2mph"
                val *= 0.6214
            case "kph2kts"
                val *= 1.852
            case "kph2mch"
                val *= 1234.8
            'mph
            case "mph2fps"
                val *= 1.46667
            case "mph2mps"
                val *= 0.44704
            case "mph2kph"
                val *= 1.60934
            case "mph2kts"
                val *= 0.868976
            case "mph2mch"
                val *= 0.00130332
            'kts
            case "kts2fps"
                val *= 1.68781
            case "kts2mps"
                val *= 0.514444
            case "kts2mph"
                val *= 1.15078
            case "kts2kph"
                val *= 1.852
            case "kts2mch"
                val *= 0.0015
            'mch
            case "mch2fps"
                val *= 1125.33
            case "mch2mps"
                val *= 343
            case "mch2mph"
                val *= 767.269
            case "mch2kph"
                val *= 1234.8
            case "mch2kts"
                val *= 666.73866
        '==TEMPERATURE===========
            'f
            case "f2c"
                val = ((val - 32) * 5) / 9
            case "f2k"
                val = ((val + 459.67) * 5) / 9
            'c
            case "c2f"
                val = ((val * 9) / 5) + 32
            case "c2k"
                val = ((val * 9) / 5) + 32
                val = ((val + 459.67) * 5) / 9
            'k
            case "k2f"
                val = ((val * 9) / 5) - 459.67
            case "k2c"
                val = ((val * 9) / 5) - 459.67
                val = ((val - 32) * 5) / 9
            case else
                val = nothing
        end select
        Return val
    end function
    
    
    
    Public Sub Main()
        VA.SetText("avcs_calc_return", nothing)
        'Debugging and Testing Check (bypassed if called via spoken command)
        dim cmdTest as string = vaProxy.Utility.ParseTokens("{CMD}")
        if (cmdTest = "")
            'VA.SetText("~avcs_cmd","what is 78° f in celcius")
            VA.SetText("~avcs_cmd","what is 3 zillion divided by 2")
            'VA.SetText("~avcs_cmd","by the way what is 3 plus 3 times by")
            '---note: this expression does not work, because it has 3 operations!!
            'VA.SetText("~avcs_cmd","what is the square root of 144 plus 3 cubed")
            VA.SetInt("AVCS_CALC_ResultsDecimalPlaces",3)
            cmdTest = VA.GetText("~avcs_cmd")
        else
            
            'Check Calc Mode state, exit of off
            if ((VA.GetBoolean("AVCS_CALC_MODE_ON")) isNot nothing)
                calcActive = VA.GetBoolean("AVCS_CALC_MODE_ON")
            end if
            
            'Provide Message for infrequent wildcard recognition when Calculator Mode OFF (prompt in case user does not known)
            if (not(calcActive))
                if ((VA.GetInt("AVCS_CALC_FAULT")) isNot nothing)
                    faultCount = VA.GetInt("AVCS_CALC_FAULT")
                    faultCount += 1
                    if (faultCount >= 3)
                        if (faultCount >= 15)
                            VA.WriteToLog("Calculation recognized, but won't be computed  (this is common when calculator is OFF)", "purple")
                            VA.SetInt("AVCS_CALC_FAULT", nothing)
                            faultCount = 0
                        end if
                    else
                        randomText = "Calculator is OFF.  To enable, say, '" + vaProxy.Utility.ParseTokens("{TXTRANDOM:Enable;Begin;Turn On}") + " Voice Calculator'"
                        VA.WriteToLog(randomText, "purple")
                    end if
                    VA.SetInt("AVCS_CALC_FAULT", faultCount)
                else
                    VA.SetInt("AVCS_CALC_FAULT", 1)
                end if
                'Exit Calc Function when Mode OFF
                Exit Sub
            end if
        end if
        
        
        'Get Custom Decimal Place Settings (if exists)
        if (VA.GetInt("AVCS_CALC_ResultsDecimalPlaces"))
            decimalPlaces = Convert.ToInt32(VA.GetInt("AVCS_CALC_ResultsDecimalPlaces"))
        end if
        
        'Parse Spoken Command for Equation(s)
        if (VA.GetText("~avcs_cmd")) isNot nothing
            
            
            'Remove large number separators & spaces from keywords and remove known "to" and "1" in operations
            cmd = VA.GetText("~avcs_cmd").Replace("swear ","square ").Replace("square root","squareroot").Replace("to the power","power").Replace("too value"," value").Replace("to value"," value").Replace("2 value"," value").Replace("value ","value").Replace(" per ","per").Replace(" ad ","add").Replace("add that 2","add that ").Replace("add that to","add that ")
            FixLargeNumberSeparator(cmd)
            if (cmd.StartsWith("fore to"))
                cmd = cmd.Substring(7, cmd.Length - 7)
            elseif (cmd.StartsWith("for to"))
                cmd = cmd.Substring(6, cmd.Length - 6)
            elseif (cmd.StartsWith("or to"))
                cmd = cmd.Substring(5, cmd.Length - 5)
            elseif (cmd.StartsWith("1 is"))
                cmd = cmd.Substring(4, cmd.Length - 4)
            end if
            
            'Evaluate spoken command split at each character and space out symbols
            charArray = cmd.ToCharArray
            cmd = ""
            for each character as string in charArray
                if (SymbolsList.Contains(character))
                    cmd += " " + character.ToString() + " "
                else
                    cmd += character.ToString()
                end if
            next
            
            'Evaluate all segments of speech split at {SPACE} and parse for operands and operators
            cmdSegments = cmd.Split(New String() {" "},StringSplitOptions.None)
            for each segment as string in cmdSegments
                if (not(segment = ""))
                    
                    'Handle numbers recognized as homonym words
                    select case segment
                        case "too"
                            segment = "2"
                        case "to"
                            segment = "2"
                        case "trees"
                            segment = "3"
                        case "tree"
                            segment = "3"
                        case "fore"
                            segment = "4"
                        case "for"
                            segment = "4"
                        case "ate"
                            segment = "8"
                        case "ape"
                            segment = "8"
                        case "valuetoo"
                            segment = "value2"
                        case "valueto"
                            segment = "value2"
                        case "valuetrees"
                            segment = "value3"
                        case "valuetree"
                            segment = "value3"
                        case "valuefore"
                            segment = "value4"
                        case "valuefor"
                            segment = "value4"
                        case "valueate"
                            segment = "value8"
                    end select
                    
                    'Handle short form of convertors
                    select case segment
                        case "c"
                            segment = "celcius"
                        case "f"
                            segment = "fahrenheit"
                        case "k"
                            segment = "kelvin"
                        case "m"
                            segment = "meters"
                    end select
                    
                    'Handle "subtract A from B" order of operations error
                    if (segment.Contains("from"))
                        if ((operand2 is nothing) and(operand1 isNot nothing) and ((operator1 isNot nothing) and (operator1.Contains("subtract"))))
                            subReverse = true
                        end if
                    end if
                    
                    'Handle "that" reference to previous calculation result
                    if (segment.Contains("that"))
                        if ((VA.GetDecimal("AVCS_CALC_LastResult")) isNot nothing)
                            segment = VA.GetDecimal("AVCS_CALC_LastResult").ToString()
                        end if
                    end if
                    
                    'Handle MEM reference to previous calculation result
                    if ((segment.StartsWith("value")) and (IsNumeric(segment.Substring(segment.Length - 1, 1))))
                        if ((VA.GetDecimal(segment)) isNot nothing)
                            segment = VA.GetDecimal(segment).ToString()
                        else
                            VA.WriteToLog(segment + " does not exist in memory", "red")
                            Exit Sub
                        end if
                    end if
                    
                    'Handle "pi" and homonym variants 
                    if (PiList.Contains(" " + segment + " "))
                        if (operand1 is nothing)
                            operand1 = Math.Pi
                        elseif (operand2 is nothing)
                            operand2 = Math.Pi
                        elseif (operand3 is nothing)
                            operand3 = Math.Pi
                        end if
                        
                    'Set Operands -------
                    elseif ((IsNumeric(segment)) and (operand1 is nothing))
                        try
                            operand1 = Convert.ToDecimal(segment)
                            operandLast = operand1
                            if (operationsOrder = 0)
                                operationsOrder = 1 'for edge case:  (b = Pi)
                            end if
                        catch
                            VA.WriteToLog("AVCS Calculation Error at Operand 1", "red")
                        end try
                    elseif ((IsNumeric(segment)) and (operand2 is nothing))
                        try
                            operand2 = Convert.ToDecimal(segment)
                            operandLast = operand2
                        catch
                            VA.WriteToLog("AVCS Calculation Error at Operand 2", "red")
                        end try
                    elseif ((IsNumeric(segment)) and (operand3 is nothing))
                        try
                            operand3 = Convert.ToDecimal(segment)
                            operandLast = operand3
                        catch
                            VA.WriteToLog("AVCS Calculation Error at Operand 3", "red")
                        end try
                        
                    'Set Operators -------
                    elseif ((OperatorsList.Contains(" " + segment + " ")) and (operator1 is nothing))
                        operator1 = segment
                        if (operationsOrder = 0)
                            operationsOrder = 2 'for edge case:  (a = Pi)
                        end if
                    elseif ((OperatorsList.Contains(" " + segment + " ")) and (operator2 is nothing))
                        operator2 = segment
                        if ((operationsOrder = 1) and (operand2 isNot nothing))
                            operationsOrder = 3 'for edge case:  (c = b and b = Pi)
                        elseif ((operationsOrder = 1) and (operand2 is nothing))
                            operationsOrder = 4 'for edge case:  (c = Pi)
                        end if
                        
                    'Set Convertors -------
                    elseif ((ConvertorsList.Contains(" " + segment + " ")) and (operator1 is nothing))
                        operator1 = segment
                        operationsOrder = 5 'for edge case null:  (no way Pi)
                    elseif ((ConvertorsList.Contains(" " + segment + " ")) and (operator2 is nothing))
                        operator2 = segment
                        
                    'Handle "..illions" -------
                    elseif (segment.EndsWith("illion"))
                        if ((operand1 isNot nothing) and (operandLast = operand1))
                            FixLargeWordNum(segment,operand1)
                        elseif ((operand2 isNot nothing) and (operandLast = operand2))
                            FixLargeWordNum(segment,operand2)
                        elseif ((operand3 isNot nothing) and (operandLast = operand3))
                            FixLargeWordNum(segment,operand3)
                        end if
                        if (segment.StartsWith("AVCS Calculation Error"))
                            VA.WriteToLog(segment, "red")
                            Exit Sub
                        end if
                    end if
                    
                end if
            next
            
            
            'Evaluate for Pi recognized as "by" (edge cases)
            if (operationsOrder < 5)
                '-- 1 operation catch
                if ((operator2 is nothing) and (operand2 is nothing))
                    if (HasPi(operator1))
                        if (operationsOrder = 1)
                            operand2 = Math.Pi
                        elseif (operationsOrder = 2)
                            operand2 = operand1
                            operand1 = Math.Pi
                        end if
                    end if
                '-- 2 operations catch
                elseif ((operator2 isNot nothing) and (operand3 is nothing))
                    if ((HasPi(operator1)) or (HasPi(operator2)))
                        if (operationsOrder = 4)
                            operand3 = operand2
                            operand2 = Math.Pi
                        elseif (operationsOrder = 3)
                            operand3 = Math.Pi
                        elseif (operationsOrder = 2)
                            operand3 = operand2
                            operand2 = operand1
                            operand1 = Math.Pi
                        end if
                    end if
                end if
            end if
            
            'Evaluate Operator 1 ======
            if (operator1 isNot nothing)
                'Handle null operands on Squared/Cubed operators
                if ((operator1.StartsWith("square")) and (not(operator1.Contains("root"))))
                    operand2 = 2
                elseif (operator1.StartsWith("cub"))
                    operand2 = 3
                'Handle Reverse order of operations on Subtract
                elseif ((operator1.Contains("subtract")) and (subReverse))
                    dim operand4 as decimal? = operand2
                    operand2 = operand1
                    operand1 = operand4
                end if
                
                'Set Operator 1 keyword and get text form of Operator/Convertor
                if (not(OperatorsList.Contains(operator1)))
                    SetConvertor(operator1,operatorText1)
                else
                    SetOperator(operator1,operatorText1)
                end if
            else
                VA.WriteToLog("AVCS Calculation Error on Null Operator 1", "red")
                Exit Sub
            end if
            
            'Evaluate Operator 2 ======
            if (operator2 isNot nothing)
                'Handle null operands on Squared/Cubed operators
                if ((operator2.StartsWith("square")) and (not(operator2.Contains("root"))))
                    operand3 = 2
                elseif (operator2.Contains("cub"))
                    operand3 = 3
                end if
                'Set Operator 2 keyword and get text form of Operator/Convertor
                if (not(OpsList.Contains(operator1)))
                    SetConvertor(operator2,operatorText2)
                    operator1 = operator1 + operator2.Substring(0, operator2.Length - 1)
                    operator2 = nothing
                else
                    SetOperator(operator2,operatorText2)
                    'Calculate Square Root on Multi-Part Equation to avoid errors on null operand3
                    'ex. 200 subtract square root of 128 = 188.69 = a-(Sqrt(b))
                    if ((operator2 = "sqr") and (operand3 is nothing))
                        equationText = operand1.ToString() + " " + operatorText1 + " " + operatorText2 + " " + operand2.ToString() + " "
                        operand2 = Math.Sqrt(operand2)
                        operator2 = nothing
                    end if
                    
                    'Calculate Percentage before to avoid order of operations and text display errors
                    'ex. 200 subtract 20 % = 160 = a-(b*(a/100))
                    if ((operator2 = "per") and (operand3 is nothing))
                        equationText = operand1.ToString() + " " + operatorText1 + " " + operand2.ToString() + " " + operatorText2 + " " + operand1.ToString() + " "
                        operand3 = operand1
                        operand2 = operand2*(operand3 / 100)
                        operand3 = nothing
                        operator2 = nothing
                    'ex. 200 subtract 20 % of 100 = 180 = a-(b*(c/100)
                    elseif ((operator2 = "per") and (operand3 isNot nothing))
                        equationText = operand1.ToString() + " " + operatorText1 + " " + operand2.ToString() + " " + operatorText2 + " " + operand3.ToString() + " "
                        operand2 = operand2*(operand3 / 100)
                        operand3 = nothing
                        operator2 = nothing
                    'ex. 4 plus 7 to the power of 3 = 347 = a+(b^c)
                    elseif (((operator2 = "exp") and (operatorText2.StartsWith("to"))) and (operand3 isNot nothing))
                        equationText = operand1.ToString() + " " + operatorText1 + " " + operand2.ToString() + " " + operatorText2 + " " + operand3.ToString() + " "
                        operand2 = Math.Pow(operand2, operand3)
                        operand3 = nothing
                        operator2 = nothing
                    'ex. 4 plus 7 cubed = 347 = a+b³   [or]   4 plus 7 squared = 53 = a+b²
                    elseif (((operator2 = "exp") and (not(operatorText2.StartsWith("to")))) and (operand3 isNot nothing))
                        equationText = operand1.ToString() + " " + operatorText1 + " " + operand2.ToString() + " " + operatorText2 + " "
                        operand2 = Math.Pow(operand2, operand3)
                        operand3 = nothing
                        operator2 = nothing
                    end if
                end if
            end if
            
            'Final Evaluation for "8" recognized as "a"
            if ((DoubleOpsList.Contains(operator1)) and (operator2 is nothing) and (operand1 isNot nothing) and (operand2 is nothing))
                if ((cmd.Contains(" a ")) or (cmd.EndsWith(" a")))
                    if (operationsOrder = 1)
                        operand2 = 8
                    else
                        operand2 = operand1
                        operand1 = 8
                    end if
                end if
            end if
            
            
            '==EVALUATE RESULT==
            if (OpsList.Contains(operator1))
                
                if (equationText = "")
                    'Build Equation Text for display in VA Event log
                    if (operand1 isNot nothing)
                        equationText += operand1.ToString() + " "
                    end if
                    if (operator1 isNot nothing)
                        equationText += operatorText1 + " "
                    end if
                    if (operand2 isNot nothing)
                        equationText += operand2.ToString() + " "
                    end if
                    if (operator2 isNot nothing)
                        equationText += operatorText2 + " "
                    end if
                    if (operand3 isNot nothing)
                        equationText += operand3.ToString() + " "
                    end if
                    
                    'Calculate Square Root on Multi-Part Equation to avoid incorrect operations math and display text
                    'ex. square root of 144 times 7 = 84 = b*(Sqrt(a))
                    if ((operator1 = "sqr") and ((operator2 isNot nothing) and (operand2 isNot nothing)))
                        equationText = operatorText1 + " " + operand1.ToString() + " " + operatorText2 + " " + operand2.ToString() + " "
                    end if
                    'Remove any whitespace between % and number(s)
                    if (equationText.Contains(" %"))
                        equationText = equationText.Replace(" %","%")
                    end if
                    
                end if
                
                'MATHS BEGIN--------------------------------
                'If there are 2 operations
                if (operator2 isNot nothing)
                    'If there are 2 operations with 3 values
                    if (operand3 isNot nothing)
                        if ((operand1 isNot nothing) and (operand2 isNot nothing))
                            result = GetResult(operator1,operand1,operand2)
                        end if
                        if (result isNot nothing)
                            result = GetResult(operator2,result,operand3)
                        end if
                    
                    'Else if there are 2 operations with 2 values
                    elseif (operand2 isNot nothing)
                        'Handle Square Root on Second Operand
                        if ((operator1 = "sqr") and (operand1 isNot nothing))
                            result = Math.Sqrt(operand1)
                        else
                            result = nothing
                        end if
                        
                        if ((operator1 = "sqr") and (result isNot nothing))
                            result = GetResult(operator2,result,operand2)
                        else
                            if ((result isNot nothing) and (operand1 isNot nothing))
                                result = GetResult(operator1,operand1,result)
                            elseif (operand1 isNot nothing)
                                result = GetResult(operator2,operand1,operand2)
                            end if
                        end if
                    end if
                
                'Else if there is 1 operations with up to 2 values
                elseif (operator1 isNot nothing)
                    if ((operand1 isNot nothing) and  (operand2 isNot nothing))
                        result = GetResult(operator1,operand1,operand2)
                        if ((operatorText1 = "cubed") or (operatorText1 = "squared"))
                            equationText = equationText.Replace(" " + operand2.ToString() + " "," ")
                        end if
                    elseif ((operand1 isNot nothing) and (not(DoubleOpsList.Contains(operator1))))
                        result = GetResult(operator1,operand1)
                        equationText = equationText.Replace(operand1.ToString() + " ","") + operand1.ToString() + " "
                    end if
                end if
            else
                'Else this is a Unit Conversion with 1 value
                if (operator1 isNot nothing)
                    if (operand1 isNot nothing)
                        result = GetConversion(operator1,operand1)
                        equationText = operand1.ToString() + " " + operatorText1 + " = "
                    end if
                else
                    VA.WriteToLog("AVCS Calculation Error on Null Operator 1", "red")
                end if
            end if
            
            
            'Evaluate Result ======
            if (result isNot nothing)
                try
                    'Try to compare decimal with integer version of itself to discover decimal places, then round
                    dim checkResult as Int32 = Convert.ToInt32(result)
                    if (result = checkResult)
                        'For TTS brevity use the one that couldn't end with .000
                        result = checkResult
                    elseif (checkResult <> result)
                        dim roundResult as Double = result
                        result = Math.Round(roundResult, decimalPlaces)
                    end if
                catch
                    'Result is a whole number already, does not need rounding for TTS brevity
                end try
                
                'Correct Pi in equation text string to 2 decimal places for TTS brevity
                if(equationText.Contains(Math.Pi.ToString()))
                    equationText = equationText.Replace(Math.Pi.ToString(), "3.14")
                end if
                
                'Get Current History Count and/or Increment
                if((VA.GetInt("AVCS_CALC_CountOperations")) isNot nothing)
                    countHistory = VA.GetInt("AVCS_CALC_CountOperations")
                    countHistory += 1
                    if (countHistory > 99)
                        countHistory = 1
                    end if
                end if
                VA.SetInt("AVCS_CALC_CountOperations", countHistory)
                
                'Save to Calc History & Display Last complete equation with result in Event Log
                if (OpsList.Contains(operator1))
                    VA.SetText("AVCS_CALC_LastOperation", equationText + "= " + result.ToString())
                    VA.SetText("AVCS_CALC_LastOperation_" + countHistory.ToString(), equationText + "= " + result.ToString())
                    VA.WriteToLog("Value " + countHistory.ToString() + " = " + equationText + "= " + result.ToString(), "green")
                else
                    VA.SetText("AVCS_CALC_LastOperation", equationText + result.ToString() + " " + operatorText2)
                    VA.SetText("AVCS_CALC_LastOperation_" + countHistory.ToString(), equationText + result.ToString() + " " + operatorText2)
                    VA.WriteToLog("Value " + countHistory.ToString() + " = " + equationText + result.ToString() + " " + operatorText2, "green")                 
                end if
                
                'Save to Memory, Set Last for "that" reference, and Return Result for TTS
                VA.SetDecimal("value" + countHistory.ToString(), result)
                VA.SetDecimal("AVCS_CALC_LastResult", result)
                VA.SetText("avcs_calc_return", result.ToString())
                
            end if
            
            
        end if
    
    End Sub
 
End Class

Comments