Author Topic: VB.net Inline Function Example (from AVCS Voice Calculator & Conversions v1.1)  (Read 5094 times)

SemlerPDX

  • Global Moderator
  • Sr. Member
  • *****
  • Posts: 280
  • Upstanding Lunatic
    • My AVCS Homepage
This is the main Inline Function (in VB.net) for my Voice Calculator & Conversions profile for VoiceAttack.

If anyone has any questions, or would like help in applying similar parsing and evaluation methods in another inline function (or even a C# Inline Function), feel free to ask and I'll help if I can.

Cheers!

(on pastebin:  https://pastebin.com/k5Ugpi7f)

Code: [Select]
'AVCS Voice Calculator & Conversions (v1.1) -- Calculate expressions with up to 2 operations and 3 values "Any way you say it"
'by SemlerPDX Mar2020/Mar2021
'VETERANS-GAMING.COM

'demonstration video on youtube:  https://youtu.be/bCjuYh92M-I

'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]*;*[-1;-2;-3;-4;-5;-6;-7;-8;-9]*

'Set text [~avcs_cmd] to '{TXTWORDTONUM:"{CMD}"}' (Trim) (Lower Case)

Imports Microsoft.VisualBasic
Imports System
Imports System.Linq
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]'

'Operational Keyword Arrays
dim OperatorsArray() as string = {
"divided",
"divide",
"times",
"multiplied",
"multiply",
"add",
"plus",
"subtract",
"subtracted",
"minus",
"-",
"%",
"percent",
"power",
"square",
"squared",
"squareroot",
"cubed",
"cubes",
"cube",
"cuban",
"cuba"
}
dim ConvertorsArray() 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 OpsArray() as string = {
"sum",
"sub",
"prd",
"dif",
"exp",
"per",
"sqr"
}
dim DoubleOpsArray() as string = {
"sum",
"sub",
"prd",
"dif"
}
dim SymbolsArray() as string = {
"°",
"%",
"^",
"-",
":",
"$",
"£",
"€",
"¢",
"¥",
"ƒ"
}
dim PiArray() 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 = ""
'Calc Systems Vars
dim operationsOrder as integer = 0
dim countHistory as integer = 1
dim countFaults as integer = 0
dim randomText as string
dim subReverse as boolean
dim calcActive as boolean


'Function to remove spaces from keywords, handle homophones & edge case words
Private Function ReplaceEdgeCaseWords(ByRef cmd as string)
if (cmd.StartsWith("ad "))
cmd = cmd.Replace("ad ","add ")
end if

cmd = cmd.Replace(
" too "," to "
).Replace(
"swear ","square "
).Replace(
"square root","squareroot"
).Replace(
"to the power","power"
).Replace(
"to value"," value"
).Replace(
"2 value"," value"
).Replace(
"value ","value"
).Replace(
"to that"," that"
).Replace(
"2 that"," that"
).Replace(
" tell them"," kelvin"
).Replace(
" per ","per"
).Replace(
" ad "," add "
).Replace(
"added to","add "
).Replace(
"added 2","add "
).Replace(
"added ","add "
).Replace(
"add that 2","add that "
).Replace(
"add that to","add that "
).Replace(
"÷"," divided by "
).Replace(
"¼","0.25"
).Replace(
"½","0.5"
).Replace(
"¾","0.75"
)
end function


'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 *= (val2 / 100)
case "exp"
val1 = Pow(val1, val2)
case "sqr"
val1 = 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
'miles
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
'knots
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
'mach
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
'kelvin
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", "by the way what is 3 plus 3 times by")
VA.SetInt("AVCS_CALC_ResultsDecimalPlaces", 2)
else
'Check Calc Mode state, exit if off
if ((VA.GetBoolean("AVCS_CALC_MODE_ON")) isNot nothing)
calcActive = VA.GetBoolean("AVCS_CALC_MODE_ON")
end if

'Provide Message for wildcard recognition when Calculator Mode OFF (infrequent prompt in case user does not know)
if (not(calcActive))
if ((VA.GetInt("AVCS_CALC_FAULT")) isNot nothing)
countFaults = VA.GetInt("AVCS_CALC_FAULT") + 1
if (countFaults >= 3)
if (countFaults >= 15)
VA.WriteToLog("Calculation recognized, but won't be computed  (this is common when calculator is OFF)", "purple")
VA.SetInt("AVCS_CALC_FAULT", nothing)
countFaults = 0
end if
else
randomText = "Calculator is OFF.  To enable, say, '" + vaProxy.Utility.ParseTokens("{TXTRANDOM:Enable;Begin;Turn On;Start}") + " Voice Calculator'"
VA.WriteToLog(randomText, "purple")
end if
VA.SetInt("AVCS_CALC_FAULT", countFaults)
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")) isNot nothing)
decimalPlaces = Convert.ToInt32(VA.GetInt("AVCS_CALC_ResultsDecimalPlaces"))
end if

'Parse Spoken Command for Equation(s)
if ((VA.GetText("~avcs_cmd")) isNot nothing)
cmd = VA.GetText("~avcs_cmd")

'Process spoken command formatting and keywords
ReplaceEdgeCaseWords(cmd)
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 (SymbolsArray.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 (segment <> "")

'Handle numbers recognized as homophone 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 last 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 "value" 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 negative temperatures
if ((operator2 isNot nothing) and (operand1 isNot nothing))
if ((operationsOrder = 2) and (ConvertorsArray.Contains(operator2)))
operator1 = operator2
operator2 = nothing
operand1 -= (operand1 * 2)
operationsOrder = 5
end if
end if

'Handle "pi" and homophone variants
if (PiArray.Contains(segment))
if (operand1 is nothing)
operand1 = Pi
elseif (operand2 is nothing)
operand2 = Pi
elseif (operand3 is nothing)
operand3 = 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 ((OperatorsArray.Contains(segment)) and (operator1 is nothing))
operator1 = segment
if (operationsOrder = 0)
operationsOrder = 2 'for edge case:  (a = Pi)
end if
elseif ((OperatorsArray.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 ((ConvertorsArray.Contains(segment)) and (operator1 is nothing))
operator1 = segment
operationsOrder = 5 'for edge case null:  (no way Pi)
elseif ((ConvertorsArray.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 = Pi
elseif (operationsOrder = 2)
operand2 = operand1
operand1 = 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 = Pi
elseif (operationsOrder = 3)
operand3 = Pi
elseif (operationsOrder = 2)
operand3 = operand2
operand2 = operand1
operand1 = 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(OperatorsArray.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(OpsArray.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 = Sqrt(operand2)
operator2 = nothing

'Calculate Percentage before to avoid order of operations and text display errors
'ex. 200 subtract 20 % = 160 = a-(b*(a/100))
elseif ((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

'Calculate Exponents before to avoid order of operations and text display errors
'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 = 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 = Pow(operand2, operand3)
operand3 = nothing
operator2 = nothing
end if
end if
end if

'Final Evaluation for "8" recognized as "a"
if ((DoubleOpsArray.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


'==CALCULATE RESULT==
if (OpsArray.Contains(operator1))
'Build Equation Text for display in VA Event log
if (equationText = "")
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 = 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 operation 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(DoubleOpsArray.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 = 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(Pi.ToString()))
equationText = equationText.Replace(Pi.ToString(), "3.14")
end if

'Get/Increment/Set Current Operations History Count
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 (OpsArray.Contains(operator1))
equationText = equationText.Replace(" ²","²").Replace(" ³","³").Replace(" ⁿ ","ⁿ").Replace("√ ","√")
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
« Last Edit: May 06, 2021, 12:53:02 AM by SemlerPDX »