# Thread: simple math string parser

1. ## simple math string parser

I need a fast and easy way to parse a math expression that only contains addition and multiplication, the numbers can have decimals.
the result is a whole number, so the decimals are only used inside the parser. here's my take:
Code:
```Private Function doMath&(ByVal Exp\$)
Dim i&, j&, a&, b&, am!, bm!, rm!, f\$

f = "x"
For j = 1 To 2
Do
i = InStr(1, Exp, f)
If i = 0 Then
Exit Do
Else
a = i - 1
b = i + 1
Do
If Mid(Exp, a, 1) = "+" Then Exit Do Else a = a - 1
Loop Until a = 0
Do
If Mid(Exp, b, 1) = "+" Then Exit Do Else b = b + 1
Loop Until b > Len(Exp)
am = CSng(Mid(Exp, a + 1, i - a - 1))
bm = CSng(Mid(Exp, i + 1, b - i - 1))
If j = 1 Then rm = am * bm Else rm = am + bm
Exp = Left(Exp, a) & CStr(rm) & Mid(Exp, b)
End If
Loop
f = "+"
Next j
doMath = CLng(Exp)
End Function```
and no, it should not require a reference/component/ocx. maybe a windows API (need to be available xp to 10)
so my question is, if theres a better way to do it, smarter and faster?
as I wrote, only * and +, but if you include / - and its better I dont mind, but Its not required.
the importance is speed more than length of code.

2. ## Re: simple math string parser

I just put together a sample grammar for VbPeg that impl +/- and mul/div on doubles along with parentheses for explicit operations precedence and then compiled the parser to mdCalc.bas.

In the sample project the grammar is called from Sub Main evaluating command line arguments and dumping the result (double value) and optionally an error from the expression parser (string).

At 497 LOC this parser seems a bit bloated for your case but works reasonable w/o external deps, here are samples of error reporting along w/ intermediate results on failure:

Code:
```C:\Work\Temp\vbpeg\test\calc2>Project1.exe 1+999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999999992.1237+67
Result: 68
Error: Overflow```
Code:
```C:\Work\Temp\vbpeg\test\calc2>Project1.exe ((1+2)*3)/2***
Result: 4.5
Error: Extra characters: ***```
You can also use VbPeg's `-public` or `-private` options to generates a public/private class module from the sample grammar (instead of a global .bas) for better encapsulation.

Code:
```Usage: VbPeg.exe [options] <in_file.peg>

Options:
-o OUTFILE      write result to OUTFILE [default: stdout]
-tree           output parse tree
-ir             output intermediate represetation
-public         emit public VB6 class module
-private        emit private VB6 class module
-module NAME    VB6 class/module name [default: OUTFILE]
-userdata TYPE  parser context's UserData member data-type [default: Variant]
-q              in quiet operation outputs only errors```
cheers,
</wqw>

3. ## Re: simple math string parser

Doesn't work,

I input 3+5*9+7+2*5
but I get the result 11, it should be 65

4. ## Re: simple math string parser

doing more tests, using Byref speed up the parsing a lot,
also, copying char by char is faster than using mid\$() strangely enough.
also slipping it to multiple functions makes it a bit faster as well.

edit: byref is not an option, destroys the string as Schmidt pointed out!

5. ## Re: simple math string parser

Here is a simple evaluating expression-parser, which can do a bit more than just "plus and mul"
(it is not performance-optimized, but quite short code-wise - able to support even simple function-calls):

Into a *.bas-Module
Code:
```Option Explicit

Public Function Eval(ByVal Expr As String)
Dim L As String, R As String
Do While HandleParentheses(Expr): Loop

If 0 Then
ElseIf Spl(Expr, "Or", L, R) Then:   Eval = Eval(L) Or Eval(R)
ElseIf Spl(Expr, "And", L, R) Then:  Eval = Eval(L) And Eval(R)
ElseIf Spl(Expr, ">=", L, R) Then:   Eval = Eval(L) >= Eval(R)
ElseIf Spl(Expr, "<=", L, R) Then:   Eval = Eval(L) <= Eval(R)
ElseIf Spl(Expr, "=", L, R) Then:    Eval = Eval(L) = Eval(R)
ElseIf Spl(Expr, ">", L, R) Then:    Eval = Eval(L) > Eval(R)
ElseIf Spl(Expr, "<", L, R) Then:    Eval = Eval(L) < Eval(R)
ElseIf Spl(Expr, "Like", L, R) Then: Eval = Eval(L) Like Eval(R)
ElseIf Spl(Expr, "&", L, R) Then:    Eval = Eval(L) & Eval(R)
ElseIf Spl(Expr, "-", L, R) Then:    Eval = Eval(L) - Eval(R)
ElseIf Spl(Expr, "+", L, R) Then:    Eval = Eval(L) + Eval(R)
ElseIf Spl(Expr, "Mod", L, R) Then:  Eval = Eval(L) Mod Eval(R)
ElseIf Spl(Expr, "\", L, R) Then:    Eval = Eval(L) \ Eval(R)
ElseIf Spl(Expr, "*", L, R) Then:    Eval = Eval(L) * Eval(R)
ElseIf Spl(Expr, "/", L, R) Then:    Eval = Eval(L) / Eval(R)
ElseIf Spl(Expr, "^", L, R) Then:    Eval = Eval(L) ^ Eval(R)
ElseIf Trim(Expr) >= "A" Then:       Eval = Fnc(Expr)
ElseIf Len(Expr) Then:               Eval = IIf(InStr(Expr, "'"), _
Replace(Trim(Expr), "'", ""), Val(Expr))
End If
End Function

Private Function HandleParentheses(Expr As String) As Boolean
Dim P As Long, i As Long, C As Long
P = InStr(Expr, "(")
If P Then HandleParentheses = True Else Exit Function

For i = P To Len(Expr)
If Mid(Expr, i, 1) = "(" Then C = C + 1
If Mid(Expr, i, 1) = ")" Then C = C - 1
If C = 0 Then Exit For
Next i

Expr = Left(Expr, P - 1) & Str(Eval(Mid(Expr, P + 1, i - P - 1))) & Mid(Expr, i + 1)
End Function

Private Function Spl(Expr As String, Op\$, L\$, R\$) As Boolean
Dim P As Long
P = InStrRev(Expr, Op, , 1)
If P Then Spl = True Else Exit Function
If P < InStrRev(Expr, "'") And InStr("*-", Op) Then P = InStrRev(Expr, "'", P) - 1

R = Mid(Expr, P + Len(Op))
L = Trim(Left\$(Expr, IIf(P > 0, P - 1, 0)))

Select Case Right(L, 1)
Case "", "+", "*", "/", "A" To "z": Spl = False
Case "-": R = "-" & R
End Select
End Function

Private Function Fnc(Expr As String)
Expr = LCase(Trim(Expr))

Select Case Left(Expr, 3)
Case "abs": Fnc = Abs(Val(Mid\$(Expr, 4)))
Case "sin": Fnc = Sin(Val(Mid\$(Expr, 4)))
Case "cos": Fnc = Cos(Val(Mid\$(Expr, 4)))
Case "atn": Fnc = Atn(Val(Mid\$(Expr, 4)))
Case "log": Fnc = Log(Val(Mid\$(Expr, 4)))
Case "exp": Fnc = Exp(Val(Mid\$(Expr, 4)))
'etc...
End Select
End Function```
Form-Code (with a few testcases - comparing outputs with the VB6-expression-solver)
Code:
```Option Explicit

'just a group of Test-calls for the simple Evaluator (in comparison to VB-outputs)
'the VB6-resolved expression is always located directly below the Eval-String to
'be able to compare the test-expressions more easily ...
'(results are printed side-by-side and should come out the same in all test-cases)

'simple operator-precedence without parentheses
Debug.Print Eval("3 + 5 * 9 + 7 + 2 * 5"), _
3 + 5 * 9 + 7 + 2 * 5

Debug.Print Eval("1 + 6 / 3 - 7"), _
1 + 6 / 3 - 7

'unary-operator test
Debug.Print Eval("-1 + -6 / 3 - -7 "), _
-1 + -6 / 3 - -7

'simple parentheses test
Debug.Print Eval("-14 / 7 * -(1 + 2)"), _
-14 / 7 * -(1 + 2)

'a complex case, including exponent-handling
Debug.Print Eval("((1 + -2) * -3 + 4) * 2 / 7 * 216 ^ (-1 / -3) "), _
((1 + -2) * -3 + 4) * 2 / 7 * 216 ^ (-1 / -3)

'operator-precedence (mainly to test Mod and Div operators)
Debug.Print Eval("27 / 3 Mod (5 \ 2) + 23"), _
27 / 3 Mod (5 \ 2) + 23

'function-calls
Debug.Print Eval("43 + -(-2 - 3) * Abs(Cos(4 * Atn(1)))"), _
43 + -(-2 - 3) * Abs(Cos(4 * Atn(1)))

'simple case, but mixed with a string-concat (math-ops have precedence)
Debug.Print Eval("5 + 3 & 2"), _
5 + 3 & 2

'simple case of a string-concat (notation for string-literals as in SQL)
Debug.Print Eval("'abc ' & '123 ' & 'xyz'"), _
"abc " & "123 " & "xyz"

'simple comparison-ops follow... (starting with a string-comparison)
Debug.Print Eval("'abc' > '123'"), _
"abc" > "123"

'comparison of numbers... math-ops have precedence
Debug.Print Eval("3 = 1 + 2"), _
3 = 1 + 2

'comparison of strings per Like-Operator...
Debug.Print Eval("'abc' Like '*b*'"), _
"abc" Like "*b*"

'comparison of strings per Like-Operator (using the "in-range" notation)
Debug.Print Eval("'3xB..foo' Like '[1-5]?[A-C]*o'"), _
"3xB..foo" Like "[1-5]?[A-C]*o"

'and here logical comparisons, involving the And-Operator (a mix of String- and Value-Compares)
Debug.Print Eval("'foobar' Like 'foo*' And 'foobar' Like '*bar' And (1 + 2) * 4 - 1 = 11"), _
"foobar" Like "foo*" And "foobar" Like "*bar" And (1 + 2) * 4 - 1 = 11
End Sub```
HTH

Olaf

6. ## Re: simple math string parser

@Olaf: Impressively short code!

FYI, tweaked a test case to "-1 + -6 / 3 - --7" and got a diff in the test results in immediate window.

Originally Posted by baka
Doesn't work,

I input 3+5*9+7+2*5
but I get the result 11, it should be 65
Sorry about that. Had a bug in the VbPeg code generator -- fixed in 0.3.2.

cheers,
</wqw>

7. ## Re: simple math string parser

thx for the evaluator, its quite neat and includes a lot of equations.
added it to one project to try the speed, but the result time is not satisfactory, so i tried to strip down your code to just work with + & * to see if i could increase performance.
this is the stripped down version:
Code:
```Public Function Eval(Expr As String)
Dim L As String, R As String

If 0 Then
ElseIf Spl(Expr, "+", L, R) Then:    Eval = Eval(L) + Eval(R)
ElseIf Spl(Expr, "*", L, R) Then:    Eval = Eval(L) * Eval(R)
ElseIf Len(Expr) Then:               Eval = Val(Expr)
End If
End Function

Private Function Spl(Expr As String, Op\$, L\$, R\$) As Boolean
Dim P As Long

P = InStrRev(Expr, Op, , 1)
If P Then Spl = True Else Exit Function

R = Mid(Expr, P + Len(Op))
L = Trim(Left\$(Expr, IIf(P > 0, P - 1, 0)))

Select Case Right(L, 1)
Case "", "+", "*": Spl = False
End Select
End Function```
still comparing the code im using, its a bit slower.
doing 1.000.000 evaluations.
my code: average 160 milliseconds
your stripped down: average 19500 milliseconds.
my code:
Code:
```Private Function solveAdd\$(Expr\$)
Dim X&, i&, a&, b&, s\$, teA\$, teB\$

X = InStr(1, Expr, "+")
a = X - 1
b = X + 1
Do
s = Mid\$(Expr, a, 1)
If s = "+" Then Exit Do Else teA = s & teA: a = a - 1
Loop Until a = 0
Do
s = Mid\$(Expr, b, 1)
If s = "+" Then Exit Do Else teB = teB & s: b = b + 1
Loop Until b > Len(Expr)
solveAdd = Left(Expr, a) & CStr(CSng(teA) + CSng(teB)) & Mid(Expr, b)
End Function

Private Function solveMul\$(Expr\$)
Dim X&, i&, a&, b&, s\$, teA\$, teB\$

X = InStr(1, Expr, "x")
a = X - 1
b = X + 1
Do
s = Mid\$(Expr, a, 1)
If s = "x" Or s = "+" Then Exit Do Else teA = s & teA: a = a - 1
Loop Until a = 0
Do
s = Mid\$(Expr, b, 1)
If s = "x" Or s = "+" Then Exit Do Else teB = teB & s: b = b + 1
Loop Until b > Len(Expr)
solveMul = Left(Expr, a) & CStr(CSng(teA) * CSng(teB)) & Mid(Expr, b)
End Function

Public Function doMath&(Expr\$)
Do
If InStr(Expr, "x") = 0 Then Exit Do Else Expr = solveMul(Expr)
Loop
Do
If InStr(Expr, "+") = 0 Then Exit Do Else Expr = solveAdd(Expr)
Loop
doMath = CLng(Expr)
End Function```
as I only need + * x

the reason I only need + and x is because its different formulas inside the game.
its information and part of the string have a "formula" that need to be converted into numbers before it show.
it could look like this: &Lx0,75& or &Lx5& or &Lx0,5+20& where L is a number of the current level, so I use another function to find the &___& and do the Replace of L than I call the math parser.

8. ## Re: simple math string parser

@baka: 160 ms is impressive! On my machine evaluating 1 mil times vResult = CDbl("0.75") (VB6 code) takes 137 ms

cheers,
</wqw>

9. ## Re: simple math string parser

yeah, the result is not saying anything as its based on your computer specs, but the comparison do.
that is why i need as fast possible, im sure that even Olaf's eval works in the game, its not that I need to call it 1 million times haha...
but its more that that, theres a lot going on in a game, so I always try to find the fastest solution for anything.
so far the method I use is good. I don't need division because, 5 / 2 is equal to 5 * 0,5 and Im the one adding the formulas.

anyway, your suggestions are not in vain, olaf' eval is very neat and if this thread stick around Im sure others could have use for it.
or even use the one I posted, to add / and - is not that hard.

10. ## Re: simple math string parser

Hi,

here my 2cents...
Code:
```Option Explicit

Private Sub Command1_Click()
Dim scrObj As Object
Dim xResult\$
Dim xText\$

'Text1= '3+5*9+7+2*5

xText = Text1.Text
Set scrObj = CreateObject("ScriptControl")
scrObj.Language = "VBScript"
scrObj.ExecuteStatement ("xResult = " & xText)

xResult = scrObj.Eval("xResult")
Text2.Text = "the result from " & xText & " is " & xResult
End Sub

Text1.Text = "3+5*9+7+2*5"
End Sub```
regards
Chris

11. ## Re: simple math string parser

yeah, we can use scriptcontrol, word, database, ocx, etc to evaluate, but I posted that I didn't want to use that, just pure vb.
anyway, to try the speed, I added the scrObj on its own to just call it once.
so the only lines are scrObj.ExecuteStatement ("xResult = " & xText) and the result.

comparison: 10,000 loops. took around 6 milliseconds using my code and 140-150 milliseconds using ScriptControl.

12. ## Re: simple math string parser

Originally Posted by baka
... tried to strip down your code to just work with + & * to see if i could increase performance.
this is the stripped down version:
Code:
```Public Function Eval(Expr As String)
Dim L As String, R As String

If 0 Then
ElseIf Spl(Expr, "+", L, R) Then:    Eval = Eval(L) + Eval(R)
ElseIf Spl(Expr, "*", L, R) Then:    Eval = Eval(L) * Eval(R)
ElseIf Len(Expr) Then:               Eval = Val(Expr)
End If
End Function

Private Function Spl(Expr As String, Op\$, L\$, R\$) As Boolean
Dim P As Long

P = InStrRev(Expr, Op, , 1)
If P Then Spl = True Else Exit Function

R = Mid(Expr, P + Len(Op))
L = Trim(Left\$(Expr, IIf(P > 0, P - 1, 0)))

Select Case Right(L, 1)
Case "", "+", "*": Spl = False
End Select
End Function```
A better "stripped down"-version could look this way:

Code:
```Public Function Eval(Expr As String) As Double
Dim L As String, R As String
If Spl(Expr, "+", L, R) Then Eval = Eval(L) + Eval(R): Exit Function
If Spl(Expr, "*", L, R) Then Eval = Eval(L) * Eval(R): Exit Function
If Len(Expr) Then Eval = Val(Expr)
End Function

Private Function Spl(Expr As String, Op\$, L\$, R\$) As Long
Spl = InStrRev(Expr, Op)
If Spl Then R = Mid\$(Expr, Spl + Len(Op)): L = Left\$(Expr, Spl - 1)
End Function```
With that change, my test-results (doing 100000 iterations) come out as:
- Eval (0.85 sec)
- doMath (1.45 sec)

And native compiled (all Options):
- Eval (0.62 sec)
- doMath (1.22 sec)

So your code is about 2 times slower - here is the test-code I was using (in a Form):
Code:
```Option Explicit

Private Sub Form_Click()
AutoRedraw = True: Cls

Dim i As Long, T!, Result
T = Timer
For i = 1 To 100000
Result = Eval("3+5*9+7+2*5+3")
Next
Print "Eval", Timer - T, Result

T = Timer
For i = 1 To 100000
Result = doMath("3+5x9+7+2x5+3")
Next
Print "doMath", Timer - T, Result
End Sub```
Edit:
And another speedup (about factor 2 again) can be achieved, when the Spl-Subfunction-calls are avoided:

Code:
```Public Function Eval(Expr As String) As Double 'that's all what is needed
Dim P As Long
P = InStr(Expr, "+"): If P Then Eval = Eval(Left\$(Expr, P - 1)) + Eval(Mid\$(Expr, P + 1)): Exit Function
P = InStr(Expr, "*"): If P Then Eval = Eval(Left\$(Expr, P - 1)) * Eval(Mid\$(Expr, P + 1)): Exit Function
If Len(Expr) Then Eval = Val(Expr)
End Function```

Olaf

13. ## Re: simple math string parser

hmm, not sure how you got those numbers. trying in my project i get
(1,000,000) loops, 325 from doMath and 5765 from (new) Eval
maybe you can take a look.

edit: I did change doMath a bit, as you can see in the posts, i figure out that Byref and Copy single ascii is faster than Byval and using Mid\$.

14. ## Re: simple math string parser

This class isn't super-rich. It handles only +, -, *, /, and ^ but it does do ( and ) as well as hex and octal and can be used locale-blind or locale-aware, even for a specific locale. Whitespace toleration seems pretty good.

Not fully tested. Not a performance screamer but probably good enough for many purposes.

Should be possible to add more operators and even named variables (by adding an = assignment operator). VFormat class also included, to balance locale handling (output as well as input).

Reposted

Changes made to a Property name (and the demo Form UI to match).

Bug in parsing ")" corrected.

Operators for integer divide \ and modulus % added though I could be happier about using % instead of Mod but right now I didn't want to add multicharacter operators.

Replaced VarXXX() OLE API calls by VB6 operations.

Other minor optimizations such as replacing some Select Case statements with On x GoTo statements. Those are slightly quicker but not really all that much so they may be a dubious choice.

15. ## Re: simple math string parser

Originally Posted by baka
hmm, not sure how you got those numbers.
By using the test-routine I've posted in #12 (you should try it).

Originally Posted by baka
trying in my project i get
(1,000,000) loops, 325 from doMath and 5765 from (new) Eval
maybe you can take a look.
I did that - and what you do in your test-loop is:
Not taking into account, that your routine *does* destroy the (ByRef) Input-Value - in a way,
that already after the first round, you will not evaluate "the original expression-formula" (6+4x6+8+2x7) anymore,
but the result (52) as the new "to evaluate input" instead.

My testcode in #12 did avoid this, by using a String-Literal for the expression (which will get "passed along" unchanged then, in the test-loop).

An easy fix for that (in your test-code) would be, when you change the signature of your Public Function to:
Public Function doMath&(ByVal Expr\$)

Then you will get the same results as I was getting with my test-routine:
- doMath being about 3 times slower in IDE-(PCode)-Mode
- and about factor 3.5 - 4 slower when native compiled with all options

Olaf

16. ## Re: simple math string parser

Originally Posted by wqweto
@Olaf: Impressively short code!
FYI, tweaked a test case to "-1 + -6 / 3 - --7" and got a diff in the test results in immediate window.
Thanks, the (also slightly performance-optimized) version below can now handle that case of "multiple-unary-minus":
Code:
```Option Explicit

Public Function Eval(ByVal Expr As String)
Do While HandleParentheses(Expr): Loop

Dim L As String, R As String
If Spl(Expr, "Or", L, R) Then:  Eval = Eval(L) Or Eval(R): Exit Function
If Spl(Expr, "And", L, R) Then: Eval = Eval(L) And Eval(R): Exit Function
If Spl(Expr, ">=", L, R) Then:  Eval = Eval(L) >= Eval(R): Exit Function
If Spl(Expr, "<=", L, R) Then:  Eval = Eval(L) <= Eval(R): Exit Function
If Spl(Expr, "=", L, R) Then:   Eval = Eval(L) = Eval(R): Exit Function
If Spl(Expr, ">", L, R) Then:   Eval = Eval(L) > Eval(R): Exit Function
If Spl(Expr, "<", L, R) Then:   Eval = Eval(L) < Eval(R): Exit Function
If Spl(Expr, "Like", L, R) Then Eval = Eval(L) Like Eval(R): Exit Function
If Spl(Expr, "&", L, R) Then:   Eval = Eval(L) & Eval(R): Exit Function
If Spl(Expr, "-", L, R) Then:   Eval = Eval(L) - Eval(R): Exit Function
If Spl(Expr, "+", L, R) Then:   Eval = Eval(L) + Eval(R): Exit Function
If Spl(Expr, "Mod", L, R) Then: Eval = Eval(L) Mod Eval(R): Exit Function
If Spl(Expr, "\", L, R) Then:   Eval = Eval(L) \ Eval(R): Exit Function
If Spl(Expr, "*", L, R) Then:   Eval = Eval(L) * Eval(R): Exit Function
If Spl(Expr, "/", L, R) Then:   Eval = Eval(L) / Eval(R): Exit Function
If Spl(Expr, "^", L, R) Then:   Eval = Eval(L) ^ Eval(R): Exit Function
If Trim\$(Expr) >= "A" Then:     Eval = Fnc(Trim\$(Expr)): Exit Function
If InStr(Expr, "'") Then Eval = Replace(Trim\$(Expr), "'", ""): Exit Function
If Len(Expr) Then Eval = Val(Replace(Expr, "--", ""))
End Function

Private Function HandleParentheses(Expr As String) As Boolean
Dim P As Long, i As Long, C As Long
P = InStr(Expr, "(")
If P Then HandleParentheses = True Else Exit Function

For i = P To Len(Expr)
If Mid\$(Expr, i, 1) = "(" Then C = C + 1
If Mid\$(Expr, i, 1) = ")" Then C = C - 1
If C = 0 Then Exit For
Next i

Expr = Left\$(Expr, P - 1) & Str(Eval(Mid\$(Expr, P + 1, i - P - 1))) & Mid\$(Expr, i + 1)
End Function

Private Function Spl(Expr As String, Op\$, L\$, R\$) As Boolean
Dim P As Long
P = InStrRev(Expr, Op, , 1)
If P Then Spl = True Else Exit Function
If P < InStrRev(Expr, "'") And InStr("*-", Op) Then P = InStrRev(Expr, "'", P) - 1

L = Trim\$(Left\$(Expr, P - 1)): R = Mid\$(Expr, P + Len(Op))

Do
Select Case Right\$(L, 1)
Case "", "+", "*", "/", "A" To "z": Spl = False: Exit Do
Case "-": L = Trim\$(Left\$(L, Len(L) - 1)): R = "-" & R
Case Else: Exit Do
End Select
Loop
End Function

Private Function Fnc(Expr As String)
Select Case LCase\$(Left\$(Expr, 3))
Case "abs": Fnc = Abs(Val(Mid\$(Expr, 4)))
Case "sin": Fnc = Sin(Val(Mid\$(Expr, 4)))
Case "cos": Fnc = Cos(Val(Mid\$(Expr, 4)))
Case "atn": Fnc = Atn(Val(Mid\$(Expr, 4)))
Case "log": Fnc = Log(Val(Mid\$(Expr, 4)))
Case "exp": Fnc = Exp(Val(Mid\$(Expr, 4)))
'etc...
End Select
End Function```
Olaf

17. ## Re: simple math string parser

that explained why it go so fast Schmidt! thanks for finding that error.
in my test project, doing 1M loops, doMath makes to 7731 and your Eval to 2409.

this thread should definitely go to codebank, especially Schmidt's code its very good.

18. ## Re: simple math string parser

I should have named the property "ReturnedTypes" something like "ParsedTypes" because it applies to the parsed numeric values in the expression rather than the calculated result. This can have an impact on the calculation's final data type, but it does not specify what type or types the result should be limited to.

19. ## Re: simple math string parser

Found a serious flaw in post #14's attachment code. Working on a fix and some optimizations.

Changed version re-posted above to replace the original.

20. ## Re: simple math string parser

very impressive dilettante. i can't understand anything on the source code but its superfast and can do a lot!
definitely a keeper if you need an expression evaluator.

21. ## Re: simple math string parser

New version in case anyone who stumbles upon this thread needs it.

A little slower, but now it can work with variables (case-insensitive, but stored uppercased, letter and letters and digits or underscore):

May still have lingering bugs... or a few new ones

22. ## Re: simple math string parser

Originally Posted by baka
... i can't understand anything on the source code but its superfast and can do a lot!
Well, the code roughly follows this process to create a queue of operands and postfix operators from the input:

https://en.wikipedia.org/wiki/Shunti...l_illustration

Then it evaluates this.

For the input:

Code:
`a=(3+5)*9\2`
The queue looks like:

Code:
```Operand: "A" (Variable name)
Operand:  3  (Integer)
Operand:  5  (Integer)
Operator: +
Operand:  9  (Integer)
Operator: *
Operand:  2  (Integer)
Operator: \
Operator: =```

23. ## Re: simple math string parser

Once I am comfortable that the logic has been debugged more thoroughly I might combine the parsing and evaluation, eliminating the queue itself entirely using a second small operand stack instead of reusing the one stack there now. That may run even faster if evaluation is coded inline, saving a procedure call for each parsed token. Not sure I'll revisit this further though.

There may be refinements or alternatives to the shunting algorithm (or better coding) that will run faster. Perhaps the oleaut32.dll value parsing could be replaced with something less generalized using inline code. That could be both faster and far more portable for non-Windows usage. Coping with locale conventions would seem to be of value, especially for programs processing user input. Chopping that out as a requirement would make VB6-code number parsing a lot simpler. Dropping the handling of E-notation for real number values and "thousands separators" simplifies things more. Making everything Double values instead of Variant could speed things a bit more.

But my days of tinkering with compiler and interpreter construction are far behind me. I haven't taken a serious look at this stuff since school back in the 1970s.

Ripping out the On X GoTo statements and putting Select Case back in would be a little slower. But not by much and it might make the code a bit clearer for anyone who wanted to study it or re-engineer it. There are a lot of unused OLE Const values that could be ripped out as well.

24. ## Re: simple math string parser

Oh yeah:

The "FIFI" LCID (Finnish, Finland) was included as an example of another number-punctuation convention.

English:

Code:
`4,294,967,295.125`
Finnish:

Code:
`4 294 967 295,125`

25. ## Re: simple math string parser

@dilettante: FYI, latest VEval 2.0.zip seems to fail on "1 + -(1)" w/ "Bad Expression"

Here is my try on the shunting-yard algorithm
Code:
```Option Explicit

Private Declare Function ArrPtr Lib "msvbvm60" Alias "VarPtr" (Ptr() As Any) As Long
Private Declare Sub GetMem4 Lib "msvbvm60" (Source As Any, Destination As Any)

Private Function SimpleEval(sText As String) As Double
Const TOK_FINAL     As Long = 0
Const TOK_RPAREN    As Long = 1
Const TOK_ADD       As Long = 2
Const TOK_MOD       As Long = 3
Const TOK_IDIV      As Long = 4
Const TOK_MUL       As Long = 5
Const TOK_UNARY     As Long = 6
Const TOK_POWER     As Long = 7
Const TOK_LPAREN    As Long = 8
Const TOK_NUM       As Long = 9
Const TOK_WHITE     As Long = 10
Static aLookup(0 To 255) As Long
Dim uSafeArray(0 To 5) As Long
Dim aTextBuf()      As Integer
Dim lPos            As Long
Dim nChar           As Long
Dim nNumChar        As Long
Dim lNumSize        As Long
Dim aValStack(0 To 1000) As Double
Dim lValIdx         As Long
Dim aOpStack(0 To 1000) As Long
Dim lOpIdx          As Long
Dim lTokPreced      As Long
Dim lPrevPreced     As Long

On Error GoTo EH
'--- one-time init of token type lookup
If aLookup(32) = 0 Then
For nChar = 0 To UBound(aLookup)
Select Case nChar
Case 40         ' "("
aLookup(nChar) = TOK_LPAREN
Case 41         ' ")"
aLookup(nChar) = TOK_RPAREN
Case 43, 45     ' "+", "-"
Case 42, 47     ' "*", "/"
aLookup(nChar) = TOK_MUL
Case 94         ' "^"
aLookup(nChar) = TOK_POWER
Case 92         ' "\"
aLookup(nChar) = TOK_IDIV
Case 37         ' "%"
aLookup(nChar) = TOK_MOD
Case 48 To 57, 46 ' "0" To "9", "."
aLookup(nChar) = TOK_NUM
Case 32, 10, 13 ' " ", vbCrLf
aLookup(nChar) = TOK_WHITE
Case Else
aLookup(nChar) = TOK_WHITE
End Select
Next
End If
'--- point aTextBuf -> sText w/o copying
uSafeArray(0) = 1                   ' cDims
uSafeArray(1) = 2                   ' cbElements
uSafeArray(3) = StrPtr(sText)       ' pvData
uSafeArray(4) = Len(sText) + 1      ' cElements
Call GetMem4(VarPtr(uSafeArray(0)), ByVal ArrPtr(aTextBuf))
For lPos = 0 To Len(sText) - 1
nChar = aTextBuf(lPos)
If nChar >= 256 Then
GoTo Continue
End If
lTokPreced = aLookup(nChar)
If lTokPreced = TOK_NUM Then
For lNumSize = 1 To 100
nNumChar = aTextBuf(lPos + lNumSize)
If nNumChar >= 256 Or aLookup(nNumChar And &HFF&) <> TOK_NUM Then
Exit For
End If
Next
lValIdx = lValIdx + 1
aValStack(lValIdx) = Val(Mid\$(sText, lPos + 1, lNumSize))
lTokPreced = TOK_NUM
lPos = lPos + lNumSize - 1
If lPrevPreced >= TOK_ADD And lPrevPreced < TOK_NUM Then
lTokPreced = TOK_UNARY
End If
ElseIf lTokPreced = TOK_WHITE Then
GoTo Continue
End If
If lTokPreced >= TOK_ADD And lTokPreced < TOK_NUM Then
If lTokPreced <> TOK_UNARY Then '--- right assoc
GoSub EvalOpStack
End If
lOpIdx = lOpIdx + 1
aOpStack(lOpIdx) = lTokPreced * &H10000 + nChar
End If
lPrevPreced = lTokPreced
Continue:
Next
lTokPreced = TOK_FINAL
GoSub EvalOpStack
SimpleEval = aValStack(lValIdx)
EH:
'--- un-peek sText
Call GetMem4(0&, ByVal ArrPtr(aTextBuf))
Exit Function
EvalOpStack:
For lOpIdx = lOpIdx To 1 Step -1
If aOpStack(lOpIdx) < lTokPreced * &H10000 Then
Exit For
End If
lValIdx = lValIdx - 1
Select Case aOpStack(lOpIdx) And &HFFFF&
Case 43         ' "+"
If aOpStack(lOpIdx) > TOK_UNARY * &H10000 Then
lValIdx = lValIdx + 1
Else
aValStack(lValIdx) = aValStack(lValIdx) + aValStack(lValIdx + 1)
End If
Case 45         ' "-"
If aOpStack(lOpIdx) > TOK_UNARY * &H10000 Then
lValIdx = lValIdx + 1
aValStack(lValIdx) = -aValStack(lValIdx)
Else
aValStack(lValIdx) = aValStack(lValIdx) - aValStack(lValIdx + 1)
End If
Case 42         ' "*"
aValStack(lValIdx) = aValStack(lValIdx) * aValStack(lValIdx + 1)
Case 47         ' "/"
aValStack(lValIdx) = aValStack(lValIdx) / aValStack(lValIdx + 1)
Case 94         ' "^"
aValStack(lValIdx) = aValStack(lValIdx) ^ aValStack(lValIdx + 1)
Case 92         ' "\"
aValStack(lValIdx) = aValStack(lValIdx) \ aValStack(lValIdx + 1)
Case 37         ' "%"
aValStack(lValIdx) = aValStack(lValIdx) Mod aValStack(lValIdx + 1)
Case 40         ' "("
lValIdx = lValIdx + 1
If lTokPreced = TOK_RPAREN Then
lOpIdx = lOpIdx - 1
Exit For
ElseIf lTokPreced > TOK_RPAREN Then
Exit For
End If
End Select
Next
Return
End Function

Debug.Assert SimpleEval("(3.5) + 2.9 * (2 + -(1 + 2))") = (3.5) + 2.9 * (2 + -(1 + 2))
Debug.Assert SimpleEval("2 \ 3 / 3") = 2 \ 3 / 3
Debug.Assert SimpleEval("2 ^ -3 ^ 4") = 2 ^ -3 ^ 4
Debug.Assert SimpleEval("2 ^ 3 \ 5 ^ 2 / 5 / 3") = 2 ^ 3 \ 5 ^ 2 / 5 / 3

Dim sExpr           As String
Dim dblTimer        As Double
Dim lIdx            As Long
Dim dblResult       As Double

sExpr = "(3.5) + 2.9 * (2 + -(1 + 2))"
dblTimer = Timer
For lIdx = 1 To 100000
dblResult = SimpleEval(sExpr)
Next
Debug.Print "SimpleEval: " & dblResult, Format\$(Timer - dblTimer, "0.000")
End Sub```
This supports same operators w/ no variables but is about as fast as VEval class and is ~150 LOC in total.

cheers,
</wqw>

26. ## Re: simple math string parser

Thanks.

I was dealing with unary minus in a trivial and naive way. I'll have to look at correcting that.

Ok, I replaced post #21 version 2.0 attachment with version 2.1 which seems to fix that issue. If I'm lucky I haven't broken something else with that patch.

27. ## Re: simple math string parser

I just updated SimpleEval above to support rest of the arith operators (on par with VEval) and noticed a couple of edge case for version 2.1 e.g.

1) 2 \ 3 / 3
2) 2 ^ -3 ^ 4

Noticed that Integer division has an intresting precedence in Arithmetic Operator Precedence table.

cheers,
</wqw>

28. ## Re: simple math string parser

Originally Posted by dilettante
If I'm lucky I haven't broken something else with that patch.
Ah, the Microsoft approach to issuing patches.

29. ## Re: simple math string parser

Good feedback.

The precedence fix should be easy enough. I think exponentiation needs to be handled as right-associative so I can look at that too. See Operator associativity.

Thanks.

30. ## Re: simple math string parser

I thought so too, but apparently the power (^) operator is left-associative in VB6.

E.g. check the output of ? 2 ^ 3 ^ 4, (2 ^ 3) ^ 4, 2 ^ (3 ^ 4)

But unary minus complicates matters a bit (it has lesser precedence): ? 2 ^ -3 ^ 4, (2 ^ -3) ^ 4, 2 ^ (-3 ^ 4)

cheers,
</wqw>

31. ## Re: simple math string parser

I haven't followed every word of this thread, but y'all are bumping up against a problem I've fought in the past: negation.

In some math books, it's taught that negation has the highest order of precedence. However, that's not how most (if not all) programming languages work. Furthermore, we must distinguish between a variable containing a negative value and a negated variable (or constant or literal constant). In other words, there's a fundamental difference between the two printed Debug statements in the following:

Code:
```
Dim i As Long
Dim j As Long

i = -5
j = 5

Debug.Print i ^ 2
Debug.Print -j ^ 2
```
To fully understand negation, we must conceptualize a negative one variable (or constant)...

Code:
```
Const NegOne = -1
```
Now, with this constant, we can conceptualize negation as a number times this constant. For instance, in the above, we can conceptualize...

Code:
```
Dim i As Long
Dim j As Long
Const NegOne = -1

i = NegOne * 5
j = 5

Debug.Print i ^ 2
Debug.Print NegOne * j ^ 2
```
And now, we can "see" that the precedence order of operations makes perfect sense. Every negation must be "conceptualized" as the number being multiplied by -1 (with the -1 on the left-hand-side, as is the negation symbol).

And, I suppose these expression evaluators should work the same way.

Best Regards,
Elroy

32. ## Re: simple math string parser

I'll try to get back to this soon. Fixes shouldn't be very hard.

Hit a deer last night and the insurance co. totaled out my car so I'm dealing with all of that right now.

33. ## Re: simple math string parser

Originally Posted by dilettante
Hit a deer last night and the insurance co. totaled out my car so I'm dealing with all of that right now.
Wow. Same deal happened to me here (UK) about 5 years ago. Pretty horrifying experience...

34. ## Re: simple math string parser

Originally Posted by dilettante
Hit a deer last night and the insurance co. totaled out my car so I'm dealing with all of that right now.
Oh crud. Sorry to hear that, dilettante. I've hit three deer in the past, and one coyote. Two of the deer and the coyote were all in the same car, Chrysler Sebring. In fact, both deer, they hit me more than me hitting them. I finally figured out that a good horn does wonders to shake them out of their headlight daze.

Best of luck with getting everything sorted.

Elroy

35. ## Re: simple math string parser

Eh, this was on a country road at 55 MPH at a point where the road is narrow and trees and brush are in quite close. Little more than a flash and the deer was right there, and I was watching intently for just this thing too.

At least the (old) car was wedge-shaped enough that the deer rolled up the hood, the windshield, and over the roof. Until I got to a phone at a 24 hour gas station (didn't have my cellphone with me) I expected minor damage. Turned out the hood was pretty messed up though it stayed down but the right headlamp cluster was gone. Minor coolant leak, but driveable. Didn't pop any airbags.

Got a repair estimate, then the insurance appraiser came out and said "It's dead, Jim" i.e. totaled. If I had made it to May the car would be 16 years old. Longer than I was with my ex wife, sad to see a "member of the family" go like that.

By the time I got home today in a new car the tow truck was just hauling the old car from my driveway. Almost done aside from getting a lien-release letter I can't find my copy of so I can turn it over to the salvage yard with the title. Well that and getting the bad news from my insurance about my new premium rates when I get my token compensation.

Amazing what they think you need in a car these days. Web browser? Seriously? BTW: this new car was built in a UK plant. Just glad to be in the position to whip out the checkbook for this sort of thing. A lot of people would be tempted to pay the \$400 buyout and then get the old car duct-taped back together.

I'll have to baby it through the break-in period so I can put 300K miles on this one too. I just need to avoid making the turbo kick in and be easy on braking.

Too late I remembered I had about \$5 in toll change I forgot to grab when I cleaned out the old buggy. At least I had just renewed my registration so I won't have to pay the higher rates on the new car for the next 11 months or so.

I'll add that though the collision didn't bother me the money has left me more than a little rattled, being cheaper than Jack Benny. But I'd been casually car shopping for years and I did all the math as well as understanding how bad a choice it is to repair cars post-collision. Been there, done that before... after a semi tractor drove over my previous car while it was parked in a grocery store parking lot. That one I drove home too, but after repairs it was never quite right and I got rid of it.

36. ## Re: simple math string parser

Originally Posted by Elroy
And now, we can "see" that the precedence order of operations makes perfect sense. Every negation must be "conceptualized" as the number being multiplied by -1 (with the -1 on the left-hand-side, as is the negation symbol).

And, I suppose these expression evaluators should work the same way.
Try these counter-samples
Code:
```    Debug.Print 2 ^ -j ^ 2
Debug.Print 2 ^ NegOne * j ^ 2
Debug.Print 2 ^ (NegOne * j) ^ 2```
All 3 return different results and you didn't mention using parens at all. It's obviously not just syntactic substition.

The correct precedence seems to be Debug.Print 2 ^ (NegOne * j ^ 2)

cheers,
</wqw>

37. ## Re: simple math string parser

Originally Posted by Elroy
Two of the deer and the coyote were all in the same car, Chrysler Sebring.
Crazy Americans. That's what happens when you let animals drive.

38. ## Re: simple math string parser

See post #21 above. New atttachment that should handle unary minus, integer divide, etc. better.

Operator Precedence is for VBA but the same as the VB6 information that I can't find online right now.

39. ## Re: simple math string parser

Originally Posted by wqweto
Try these counter-samples
Code:
```    Debug.Print 2 ^ -j ^ 2
Debug.Print 2 ^ NegOne * j ^ 2
Debug.Print 2 ^ (NegOne * j) ^ 2```
All 3 return different results and you didn't mention using parens at all. It's obviously not just syntactic substition.

The correct precedence seems to be Debug.Print 2 ^ (NegOne * j ^ 2)

cheers,
</wqw>
Yeah, I actually thought about that after I made that post. When a negation actually IS the exponent, it apparently does have a higher precedence. However, your example is just weird. Although, with enough thought, it does make sense, sort of. It places a double-condition on negation. Basically, the expression is evaluated for negation to see if anything can be executed with negation having lower precedence. And then, it's re-evaluated to see if it can be evaluated with negation having higher precedence. And apparently, that's independent of any left-to-right evaluation. And that's basically what you outlined in your last sentence.

Best Regards,
Elroy

40. ## Re: simple math string parser

Originally Posted by dilettante
Operator Precedence is for VBA but the same as the VB6 information that I can't find online right now.
Bookmark this: Office 2010's Operator Precedence documentation.

#### Posting Permissions

• You may not post new threads
• You may not post replies
• You may not post attachments
• You may not edit your posts
•