|
A math expression evaluator is working so nicely. When i look at the code I don't understand how you come up with the state table numbers?
Dim state(,) As Integer = {{2, 4, 1, 1, 4, 6, 7}, _
{2, 3, 3, 3, 3, 3, 3}, _
{1, 1, 1, 1, 1, 1, 1}, _
{2, 4, 5, 5, 4, 5, 5}, _
{1, 1, 1, 1, 1, 1, 1}, _
{1, 1, 1, 1, 1, 1, 1}, _
{1, 1, 1, 1, 1, 1, 1}}
|
|
|
|
|
calc.evaluate("25!-24!*25") says -2147483648 instead of 0
|
|
|
|
|
-2147483648 seems like lower bound for integer.. 25! is a huge number..
Burak SARICA
|
|
|
|
|
I can't get my calculator to give decimals
Part of the code is:
textbox2.text=int(textbox1.text / 10)
if i type ex. 56 it gives 5 for 83456 it gives 83456 and so on...
how do i make the decimals???
David Dlaka
|
|
|
|
|
|
This is just what we have been looking for .
I have made some mods to include a stack etc so we can go back and use or mod any calcs.
It would be quicker to use if all inputs used the keybord ie
1+2+3+4+5+6 then insted of using the mouse just press "Enter" or "Return" to do the evaluation.
Cnn this be achived ?
If so can some one give me a hint.
Thanks
|
|
|
|
|
just put a button to do the funtion and then click enter thats one simple way that worked for me
David Dlaka
|
|
|
|
|
Anyone knows a good evaluator to solve equations ?
eg. x+5=10
|
|
|
|
|
Hey guys, I've done some changes to this excelent parser to get ternary expressions evaluated. It must be in the following way {contition ? trueValue : falseValue }
Supported operators
<, >, ==, !=, <=, >=, &, |, !
Sqare parenthesis "[ ]" are used for grouping conditions
Example : {(6+5) * 8 > 5 & ![(6+4) /2 > 5] | 7*3-5 < 15 ? 9 * 2 : 6 / 4}
Here is the code
Option Strict On
Imports System.Collections
Imports System.Collections.Generic
Imports System.Text.RegularExpressions
Namespace ExpressionEvaluator
Public Class ExpressionEvaluator
Private Class eeSymbol
Implements IComparer
Public Token As String
Public Cls As ExpressionEvaluator.TOKENCLASS
Public PrecedenceLevel As PRECEDENCE
Public tag As String
Public Delegate Function compare_function(ByVal x As Object, ByVal y As Object) As Integer
Public Overridable Overloads Function compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare
Dim asym, bsym As eeSymbol
asym = CType(x, eeSymbol)
bsym = CType(y, eeSymbol)
If asym.Token > bsym.Token Then Return 1
If asym.Token < bsym.Token Then Return -1
If asym.PrecedenceLevel = -1 Or bsym.PrecedenceLevel = -1 Then Return 0
If asym.PrecedenceLevel > bsym.PrecedenceLevel Then Return 1
If asym.PrecedenceLevel < bsym.PrecedenceLevel Then Return -1
Return 0
End Function
End Class
Private Enum PRECEDENCE
NONE = 0
LEVEL0 = 1
LEVEL1 = 2
LEVEL2 = 3
LEVEL3 = 4
LEVEL4 = 5
LEVEL5 = 6
End Enum
Private Enum TOKENCLASS
KEYWORD = 1
IDENTIFIER = 2
NUMBER = 3
[OPERATOR] = 4
PUNCTUATION = 5
End Enum
Private m_tokens As Collection
Private m_State(,) As Integer
Private m_KeyWords() As String
Private m_colstring As String
Private Const ALPHA As String = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"
Private Const DIGITS As String = "#0123456789"
Private m_funcs() As String = {"sin", "cos", "tan", "arcsin", "arccos", _
"arctan", "sqrt", "max", "min", "floor", _
"ceiling", "log", "log10", _
"ln", "round", "abs", "neg", "pos"}
Private m_operators As ArrayList
Private m_stack As New Stack()
Private Sub init_operators()
Dim op As eeSymbol
m_operators = New ArrayList()
op = New eeSymbol()
op.Token = "-"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL1
m_operators.Add(op)
op = New eeSymbol()
op.Token = "+"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL1
m_operators.Add(op)
op = New eeSymbol()
op.Token = "*"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
op = New eeSymbol()
op.Token = "/"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
op = New eeSymbol()
op.Token = "\"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
op = New eeSymbol()
op.Token = "%"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
op = New eeSymbol()
op.Token = "^"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL3
m_operators.Add(op)
op = New eeSymbol()
op.Token = "!"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL5
m_operators.Add(op)
op = New eeSymbol()
op.Token = "&"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL5
m_operators.Add(op)
op = New eeSymbol()
op.Token = "-"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL4
m_operators.Add(op)
op = New eeSymbol()
op.Token = "+"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL4
m_operators.Add(op)
op = New eeSymbol()
op.Token = "("
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL5
m_operators.Add(op)
op = New eeSymbol()
op.Token = ")"
op.Cls = TOKENCLASS.[OPERATOR]
op.PrecedenceLevel = PRECEDENCE.LEVEL0
m_operators.Add(op)
m_operators.Sort(op)
End Sub
Public Function evaluate(ByVal expression As String) As Double
Dim symbols As Queue
Try
If Regex.IsMatch(expression, "[{].*[}]") Then
' Created by Jorge Gaona
' 06/08/2007
Dim s, e As Integer
s = expression.IndexOf("{")
e = expression.LastIndexOf("}")
expression = Regex.Replace(expression, "[{].*[}]", evaluateCondition(expression.Substring(s + 1, e - s - 1)))
End If
If IsNumeric(expression) Then Return CType(expression, Double)
calc_scan(expression, symbols)
Return level0(symbols)
Catch
'MsgBox("Invalid expression")
Throw New Exception("Invalid Expression")
End Try
End Function
Public Function evaluateCondition(ByVal expression As String) As String
' Created by Jorge Gaona
' 6/8/2007
Try
Dim condition, trueValue, falseValue As String
'[{]+\w+[}]'
condition = expression.Split(CChar("?"))(0).Trim()
trueValue = expression.Split(CChar("?"))(1).Split(CChar(":"))(0).Trim()
falseValue = expression.Split(CChar("?"))(1).Split(CChar(":"))(1).Trim()
Dim operatorStack As New List(Of String)
Dim postfix As New List(Of String)
Dim logicOperator, character, item As String
'Dim logicPrecedence As Int16
item = ""
logicOperator = ""
For i As Integer = 0 To condition.Length - 1
character = condition.Substring(i, 1)
Select Case character
Case "<", ">", "!", "="
If i + 1 <= condition.Length Then
If condition.Substring(i + 1, 1) = "=" Then
logicOperator = character & "="
i = i + 1
Else
logicOperator = character
End If
End If
If item.Trim <> "" Then
postfix.Add(item.Trim)
item = ""
End If
Case "]", "&", "|", "["
logicOperator = character
If item.Trim <> "" Then
postfix.Add(item.Trim)
item = ""
End If
Case Else
item += character
End Select
Dim insert As Boolean
If logicOperator <> "" Then
Do
If operatorStack.Count > 0 And logicOperator <> "[" Then
If logicOperator <> "]" Then
If getLogicOperatorPrecedence(operatorStack(operatorStack.Count - 1).ToString) _
> getLogicOperatorPrecedence(logicOperator) Then
postfix.Add(operatorStack(operatorStack.Count - 1))
operatorStack.RemoveAt(operatorStack.Count - 1)
insert = False
Else
operatorStack.Add(logicOperator)
insert = True
logicOperator = ""
End If
Else
If operatorStack(operatorStack.Count - 1).ToString <> "[" Then
postfix.Add(operatorStack(operatorStack.Count - 1))
operatorStack.RemoveAt(operatorStack.Count - 1)
insert = False
Else
operatorStack.RemoveAt(operatorStack.Count - 1)
'operatorStack.Add(logicOperator)
insert = True
logicOperator = ""
End If
End If
Else
operatorStack.Add(logicOperator)
insert = True
logicOperator = ""
End If
Loop While Not insert
End If
Next i
If item.Trim <> "" Then
postfix.Add(item.Trim)
End If
For i As Integer = operatorStack.Count - 1 To 0 Step -1
postfix.Add(operatorStack(i))
operatorStack.RemoveAt(i)
Next
For i As Integer = 0 To postfix.Count - 1
If postfix(i).ToString <> "<" And postfix(i).ToString <> "<=" And postfix(i).ToString <> ">" And postfix(i).ToString <> ">=" _
And postfix(i).ToString <> "==" And postfix(i).ToString <> "!=" And postfix(i).ToString <> "!" And postfix(i).ToString <> "&" _
And postfix(i).ToString <> "|" And postfix(i).ToString <> "" And postfix(i).ToString.ToLower <> "true" And postfix(i).ToString.ToLower <> "false" Then
postfix(i) = evaluate(postfix(i).ToString).ToString
End If
Next
Dim elem1 As Object = Nothing
Dim pos1 As Integer = 0
Dim elem2 As Object = Nothing
Dim pos2 As Integer = 0
Do
For i As Integer = 0 To postfix.Count - 1
Select Case postfix(i)
Case "<", ">", "<=", ">=", "!=", "==", "&", "|"
elem1 = evaluateLogicExpression(elem2, elem1, postfix(i))
postfix(pos1) = elem1.ToString
postfix.RemoveAt(pos2)
postfix.RemoveAt(i - 1)
Exit For
Case "!"
elem1 = evaluateLogicExpression(elem2, elem1, postfix(i))
postfix(pos1) = elem1.ToString
postfix.RemoveAt(i)
Exit For
Case Else
elem2 = elem1
pos2 = pos1
elem1 = CObj(postfix(i))
pos1 = i
End Select
Next
Loop While postfix.Count > 1
If CBool(postfix(0)) Then
Return trueValue
Else
Return falseValue
End If
Catch
'MsgBox("Invalid expression")
Throw New Exception("Invalid Expression")
Return ""
End Try
End Function
Function evaluateLogicExpression(ByVal val1 As Object, ByVal val2 As Object, ByVal logicOperator As String) As Boolean
' Created by Jorge Gaona
' 6/8/2007
Try
Select Case logicOperator
Case "<"
Return CDbl(val1) < CDbl(val2)
Case ">"
Return CDbl(val1) > CDbl(val2)
Case "<="
Return CDbl(val1) <= CDbl(val2)
Case ">="
Return CDbl(val1) >= CDbl(val2)
Case "!"
Return Not CBool(val2)
Case "!="
Return Not val1.Equals(val2)
Case "=="
Return val1.Equals(val2)
Case "&"
Return CBool(val1) And CBool(val2)
Case "|"
Return CBool(val1) Or CBool(val2)
Case Else
Return False
End Select
Catch
'MsgBox("Invalid expression")
Throw New Exception("Invalid Expression")
End Try
End Function
Function getLogicOperatorPrecedence(ByVal logicOperator As String) As Integer
' Created by Jorge Gaona
' 6/8/2007
Select Case logicOperator
Case "<", ">", "==", "!=", "<=", ">="
Return 3
Case "]"
Return 5
Case "&"
Return 2
Case "|"
Return 1
Case "!"
Return 4
Case "["
Return 0
Case Else
Return 0
End Select
End Function
Private Function calc_op(ByVal op As eeSymbol, ByVal operand1 As Double, Optional ByVal operand2 As Double = Nothing) As Double
Select Case op.Token.ToLower
Case "&" ' sample to show addition of custom operator
Return 5
Case "^"
Return (operand1 ^ operand2)
Case "+"
Select Case op.PrecedenceLevel
Case PRECEDENCE.LEVEL1
Return (operand2 + operand1)
Case PRECEDENCE.LEVEL4
Return operand1
End Select
Case "-"
Select Case op.PrecedenceLevel
Case PRECEDENCE.LEVEL1
Return (operand1 - operand2)
Case PRECEDENCE.LEVEL4
Return -1 * operand1
End Select
Case "*"
Return (operand2 * operand1)
Case "/"
Return (operand1 / operand2)
Case "\"
Return (CLng(operand1) \ CLng(operand2))
Case "%"
Return (operand1 Mod operand2)
Case "!"
Dim i As Integer
Dim res As Double = 1
If operand1 > 1 Then
For i = CInt(operand1) To 1 Step -1
res = res * i
Next
End If
Return (res)
End Select
End Function
Private Function calc_function(ByVal func As String, ByVal args As Collection) As Double
Select Case func.ToLower
Case "cos"
Return (Math.Cos(CDbl(args(1))))
Case "sin"
Return (Math.Sin(CDbl(args(1))))
Case "tan"
Return (Math.Tan(CDbl(args(1))))
Case "floor"
Return (Math.Floor(CDbl(args(1))))
Case "ceiling"
Return (Math.Ceiling(CDbl(args(1))))
Case "max"
Return (Math.Max(CDbl(args(1)), CDbl(args(2))))
Case "min"
Return (Math.Min(CDbl(args(1)), CDbl(args(2))))
Case "arcsin"
Return (Math.Asin(CDbl(args(1))))
Case "arccos"
Return (Math.Acos(CDbl(args(1))))
Case "arctan"
Return (Math.Atan(CDbl(args(1))))
Case "sqrt"
Return (Math.Sqrt(CDbl(args(1))))
Case "log"
Return (Math.Log10(CDbl(args(1))))
Case "log10"
Return (Math.Log10(CDbl(args(1))))
Case "abs"
Return (Math.Abs(CDbl(args(1))))
Case "round"
Return (Math.Round(CDbl(args(1))))
Case "ln"
Return (Math.Log(CDbl(args(1))))
Case "neg"
Return (-1 * CDbl(args(1)))
Case "pos"
Return (+1 * CDbl(args(1)))
End Select
End Function
Private Function identifier(ByVal token As String) As Double
Select Case token.ToLower
Case "e"
Return Math.E
Case "pi"
Return Math.PI
Case Else
' look in symbol table....?
End Select
End Function
Private Function is_operator(ByVal token As String, Optional ByVal level As PRECEDENCE = CType(-1, PRECEDENCE), Optional ByRef [operator] As eeSymbol = Nothing) As Boolean
Try
Dim op As New eeSymbol()
op.Token = token
op.PrecedenceLevel = level
op.tag = "test"
Dim ir As Integer = m_operators.BinarySearch(op, op)
If ir > -1 Then
[operator] = CType(m_operators(ir), eeSymbol)
Return True
End If
Return False
Catch
Return False
End Try
End Function
Private Function is_function(ByVal token As String) As Boolean
Try
Dim lr As Integer = Array.BinarySearch(m_funcs, token.ToLower)
Return (lr > -1)
Catch
Return False
End Try
End Function
Public Function calc_scan(ByVal line As String, ByRef symbols As Queue) As Boolean
Dim sp As Integer ' start position marker
Dim cp As Integer ' current position marker
Dim col As Integer ' input column
Dim lex_state As Integer
'Dim cls As TOKENCLASS
Dim cc As Char
Dim token As String
symbols = New Queue()
line = line & " " ' add a space as an end marker
sp = 0
cp = 0
lex_state = 1
Do While cp <= line.Length - 1
cc = line.Chars(cp)
' if cc is not found then IndexOf returns -1 giving col = 2.
col = m_colstring.IndexOf(cc) + 3
' set the input column
Select Case col
Case 2 ' cc wasn't found in the column string
If ALPHA.IndexOf(Char.ToUpper(cc)) > 0 Then ' letter column?
col = 1
ElseIf DIGITS.IndexOf(Char.ToUpper(cc)) > 0 Then ' number column?
col = 2
Else ' everything else is assigned to the punctuation column
col = 6
End If
Case Is > 5 ' cc was found and is > 5 so must be in operator column
col = 7
' case else ' cc was found - col contains the correct column
End Select
' find the new state based on current state and column (determined by input)
lex_state = m_State(lex_state - 1, col - 1)
Select Case lex_state
Case 3 ' function or variable end state
' TODO variables aren't supported but substitution
' could easily be performed here or after
' tokenization
Dim sym As New eeSymbol()
sym.Token = line.Substring(sp, cp - sp)
If is_function(sym.Token) Then
sym.Cls = TOKENCLASS.KEYWORD
Else
sym.Cls = TOKENCLASS.IDENTIFIER
End If
symbols.Enqueue(sym)
lex_state = 1
cp = cp - 1
Case 5 ' number end state
Dim sym As New eeSymbol()
sym.Token = line.Substring(sp, cp - sp)
sym.Cls = TOKENCLASS.NUMBER
symbols.Enqueue(sym)
lex_state = 1
cp = cp - 1
Case 6 ' punctuation end state
Dim sym As New eeSymbol()
sym.Token = line.Substring(sp, cp - sp + 1)
sym.Cls = TOKENCLASS.PUNCTUATION
symbols.Enqueue(sym)
lex_state = 1
Case 7 ' operator end state
Dim sym As New eeSymbol()
sym.Token = line.Substring(sp, cp - sp + 1)
sym.Cls = TOKENCLASS.[OPERATOR]
symbols.Enqueue(sym)
lex_state = 1
End Select
cp += 1
If lex_state = 1 Then sp = cp
Loop
Return True
End Function
Private Sub init()
Dim op As eeSymbol
Dim state(,) As Integer = {{2, 4, 1, 1, 4, 6, 7}, _
{2, 3, 3, 3, 3, 3, 3}, _
{1, 1, 1, 1, 1, 1, 1}, _
{2, 4, 5, 5, 4, 5, 5}, _
{1, 1, 1, 1, 1, 1, 1}, _
{1, 1, 1, 1, 1, 1, 1}, _
{1, 1, 1, 1, 1, 1, 1}}
init_operators()
m_State = state
m_colstring = Chr(9) & " " & ".()"
For Each op In m_operators
m_colstring = m_colstring & op.Token
Next
Array.Sort(m_funcs)
m_tokens = New Collection()
End Sub
Public Sub New()
init()
End Sub
#Region "Recusrsive Descent Parsing Functions"
Private Function level0(ByRef tokens As Queue) As Double
Return level1(tokens)
End Function
Private Function level1_prime(ByRef tokens As Queue, ByVal result As Double) As Double
Dim symbol, [operator] As eeSymbol
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Return result
End If
' binary level1 precedence operators....+, -
If is_operator(symbol.Token, PRECEDENCE.LEVEL1, [operator]) Then
tokens.Dequeue()
result = calc_op([operator], result, level2(tokens))
result = level1_prime(tokens, result)
End If
Return result
End Function
Private Function level1(ByRef tokens As Queue) As Double
Return level1_prime(tokens, level2(tokens))
End Function
Private Function level2(ByRef tokens As Queue) As Double
Return level2_prime(tokens, level3(tokens))
End Function
Private Function level2_prime(ByRef tokens As Queue, ByVal result As Double) As Double
Dim symbol, [operator] As eeSymbol
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Return result
End If
' binary level2 precedence operators....*, /, \, %
If is_operator(symbol.Token, PRECEDENCE.LEVEL2, [operator]) Then
tokens.Dequeue()
result = calc_op([operator], result, level3(tokens))
result = level2_prime(tokens, result)
End If
Return result
End Function
Private Function level3(ByRef tokens As Queue) As Double
Return level3_prime(tokens, level4(tokens))
End Function
Private Function level3_prime(ByRef tokens As Queue, ByVal result As Double) As Double
Dim symbol, [operator] As eeSymbol
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Return result
End If
' binary level3 precedence operators....^
If is_operator(symbol.Token, PRECEDENCE.LEVEL3, [operator]) Then
tokens.Dequeue()
result = calc_op([operator], result, level4(tokens))
result = level3_prime(tokens, result)
End If
Return result
End Function
Private Function level4(ByRef tokens As Queue) As Double
Return level4_prime(tokens)
End Function
Private Function level4_prime(ByRef tokens As Queue) As Double
Dim symbol, [operator] As eeSymbol
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Throw New System.Exception("Invalid expression.")
End If
' unary level4 precedence right associative operators.... +, -
If is_operator(symbol.Token, PRECEDENCE.LEVEL4, [operator]) Then
tokens.Dequeue()
Return calc_op([operator], level5(tokens))
Else
Return level5(tokens)
End If
End Function
Private Function level5(ByVal tokens As Queue) As Double
Return level5_prime(tokens, level6(tokens))
End Function
Private Function level5_prime(ByVal tokens As Queue, ByVal result As Double) As Double
Dim symbol, [operator] As eeSymbol
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Return result
End If
' unary level5 precedence left associative operators.... !
If is_operator(symbol.Token, PRECEDENCE.LEVEL5, [operator]) Then
tokens.Dequeue()
Return calc_op([operator], result)
Else
Return result
End If
End Function
Private Function level6(ByRef tokens As Queue) As Double
Dim symbol As eeSymbol
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Throw New System.Exception("Invalid expression.")
Return 0
End If
Dim val As Double
' constants, identifiers, keywords, -> expressions
If symbol.Token = "(" Then ' opening paren of new expression
tokens.Dequeue()
val = level0(tokens)
symbol = CType(tokens.Dequeue, eeSymbol)
' closing paren
If symbol.Token <> ")" Then Throw New System.Exception("Invalid expression.")
Return val
Else
Select Case symbol.Cls
Case TOKENCLASS.IDENTIFIER
tokens.Dequeue()
Return identifier(symbol.Token)
Case TOKENCLASS.KEYWORD
tokens.Dequeue()
Return calc_function(symbol.Token, arguments(tokens))
Case TOKENCLASS.NUMBER
tokens.Dequeue()
m_stack.Push(CDbl(symbol.Token))
Return CDbl(symbol.Token)
Case Else
Throw New System.Exception("Invalid expression.")
End Select
End If
End Function
Private Function arguments(ByVal tokens As Queue) As Collection
Dim symbol As eeSymbol
Dim args As New Collection()
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, eeSymbol)
Else
Throw New System.Exception("Invalid expression.")
Return Nothing
End If
Dim val As Double
If symbol.Token = "(" Then
tokens.Dequeue()
args.Add(level0(tokens))
symbol = CType(tokens.Dequeue, eeSymbol)
Do While symbol.Token <> ")"
If symbol.Token = "," Then
args.Add(level0(tokens))
Else
Throw New System.Exception("Invalid expression.")
Return Nothing
End If
symbol = CType(tokens.Dequeue, eeSymbol)
Loop
Return args
Else
Throw New System.Exception("Invalid expression.")
Return Nothing
End If
End Function
#End Region
End Class
End Namespace
Pollirrata
|
|
|
|
|
shouldn't it remain in the letter state? that way, word variable ending in a number is still treated in one word? given your state table "variable1" will be parsed as two tokens instead of one
(x-a)(x-b)(x-c)...(x-z)
|
|
|
|
|
I think there is a problem with the evaluation of decimal numbers when the pc 's setting are on "coma" for decimal numbers instead of a "point" like in english.
scalpa
http://www.scalpa.info
|
|
|
|
|
It's because of the m_collstring(initialized in the init() sub) assumes the number decimal separator is a dot '.'. This could be easily fixed by initializing the m_colstring according to your system, ie:
m_colstring = Chr(9) & " " & System.Globalization.NumberFormatInfo.CurrentInfo.NumberDecimalSeparator & "()"
|
|
|
|
|
Ruberts approach is one way, but it does not solve the issue, that if you have double numbers in your formula, they might be converted wrong.
What I would suggest, is to write all formulas in the InvariantCulture, e.g. Numbers always with the dot (12.95 instead of 12,95) and then set the current culture to invariant.
CultureInfo current = Thread.CurrentThread.CurrentCulture;
Thread.CurrentThread.CurrentCulture = CultureInfo.InvariantCulture;
Calculator.mcCalc calculator = new Calculator.mcCalc();
price = calculator.evaluate("123 * 0.15");
Thread.CurrentThread.CurrentCulture = current;
This worked for me.
|
|
|
|
|
This expression evaluator is very functional.
Great work!!
Thanks!
Jose
|
|
|
|
|
I agree... simple, functional, comprhensible, implementable. Thumb up!
|
|
|
|
|
I try to use your evaluator to evaluate theese kind of strings : ( 8 * 0.001 ) + ( 5 * 1 ) + ( 5 * 100 ) + ( 5 * 10 ) + ( 1 * 1 000 ) + ( 9 * 0.01 ) + ( 0 * 0.1 ) and i ve got a message telling it's invalid !!!!!!!
I think it's a bug because when i try with a shorter one, it works fine (example : ( 1 * 100 ) + ( 7 * 10 ) + ( 3 * 1 )
quisnam igitur sanus ?
|
|
|
|
|
Problem is the space in your one thousand value.
Should be 1000 not 1 000.
The space makes it think it is dealing with two numbers without an operator, clearly an Invalid Expression.
|
|
|
|
|
I f I m using Math.Round(,0), then it will give the answer of 25.5 to 25 instead of 26. How this problem cann't be saved?
|
|
|
|
|
I dont understand anything to ur code but it works greatly
I needed this to implement a skin motor in my media center: www.mydomotix.com
Thanks again
Olixelle
|
|
|
|
|
I'm having trouble adding a single variable into this code. I need to add an X value into the code which will be known when the code runs, so it will need to be passed as into the function. Please help.
The langauge all programmers know best is profanity.
|
|
|
|
|
I built an interface to this calculator over a year ago that lets you type in variables. You can type in "X" or "Bills" or "Crazy Something" or anything pretty much that isn't a number and it will treat it as a variable unless its a function like sqrt().
Check out PoolwarePack at planet source code. It contains 10 programs, the one you want is called SciONE_Calculator.
|
|
|
|
|
Hi,
Very nice piece of code.
I altered it a bit with the code from the previous posts and changed a few things.
- Bugfixed of previous posts in this forum (Also Globalization support)
- support for EXP.
- Don't allow punctuation, as this is prawn to error.
If you would set CultureInfo to use comma as decimal point an you enter by accident a point in your formula, then the code just skiped the point. So 2*2.5 would result in 50.
Now the code will throw an exception, wich is better IMHO.
- Change in the class and the test Code, to work with exceptions instead of messagebox in Class.
I hope this helps to get to a better solution for everybody.
Hereby the code
Class:
<br />
Option Strict On<br />
Imports System.Globalization<br />
Imports System.Threading<br />
<br />
Public Class mcCalc<br />
<br />
Private Class mcSymbol<br />
Implements IComparer<br />
<br />
Public Token As String<br />
Public Cls As mcCalc.TOKENCLASS<br />
Public PrecedenceLevel As PRECEDENCE<br />
Public tag As String<br />
<br />
Public Delegate Function compare_function(ByVal x As Object, ByVal y As Object) As Integer<br />
<br />
Public Overridable Overloads Function compare(ByVal x As Object, ByVal y As Object) As Integer Implements IComparer.Compare<br />
<br />
Dim asym, bsym As mcSymbol<br />
asym = CType(x, mcSymbol)<br />
bsym = CType(y, mcSymbol)<br />
<br />
<br />
If asym.Token > bsym.Token Then Return 1<br />
<br />
If asym.Token < bsym.Token Then Return -1<br />
<br />
If asym.PrecedenceLevel = -1 Or bsym.PrecedenceLevel = -1 Then Return 0<br />
<br />
If asym.PrecedenceLevel > bsym.PrecedenceLevel Then Return 1<br />
<br />
If asym.PrecedenceLevel < bsym.PrecedenceLevel Then Return -1<br />
<br />
Return 0<br />
<br />
End Function<br />
<br />
End Class<br />
<br />
Private Enum PRECEDENCE<br />
NONE = 0<br />
LEVEL0 = 1<br />
LEVEL1 = 2<br />
LEVEL2 = 3<br />
LEVEL3 = 4<br />
LEVEL4 = 5<br />
LEVEL5 = 6<br />
End Enum<br />
<br />
Private Enum TOKENCLASS<br />
KEYWORD = 1<br />
IDENTIFIER = 2<br />
NUMBER = 3<br />
OPERATOR = 4<br />
PUNCTUATION = 5<br />
End Enum<br />
<br />
Private m_tokens As Collection<br />
Private m_State(,) As Integer<br />
Private m_KeyWords() As String<br />
Private m_colstring As String<br />
Private Const ALPHA As String = "_ABCDEFGHIJKLMNOPQRSTUVWXYZ"<br />
Private Const DIGITS As String = "#0123456789"<br />
<br />
Private m_funcs() As String = {"sin", "cos", "tan", "arcsin", "arccos", _<br />
"arctan", "sqrt", "max", "min", "floor", _<br />
"ceiling", "log", "log10", "ln", _<br />
"exp", "round", "abs", "neg", "pos"}<br />
<br />
Private m_operators As ArrayList<br />
<br />
Private m_stack As New Stack()<br />
<br />
Private Sub init_operators()<br />
<br />
Dim op As mcSymbol<br />
<br />
m_operators = New ArrayList()<br />
<br />
op = New mcSymbol()<br />
op.Token = "-"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL1<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "+"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL1<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "*"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL2<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "/"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL2<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "\"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL2<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "%"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL2<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "^"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL3<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "!"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL5<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "&"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL5<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "-"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL4<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "+"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL4<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = "("<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL5<br />
m_operators.Add(op)<br />
<br />
op = New mcSymbol()<br />
op.Token = ")"<br />
op.Cls = TOKENCLASS.OPERATOR<br />
op.PrecedenceLevel = PRECEDENCE.LEVEL0<br />
m_operators.Add(op)<br />
<br />
m_operators.Sort(op)<br />
End Sub<br />
<br />
<br />
Public Function evaluate(ByVal expression As String) As Double<br />
Dim symbols As Queue<br />
<br />
Try<br />
If IsNumeric(expression) Then Return CType(expression, Double)<br />
<br />
calc_scan(expression, symbols)<br />
<br />
Return level0(symbols)<br />
<br />
Catch ex As Exception<br />
<br />
Throw New System.Exception(ex.Message)<br />
<br />
End Try<br />
<br />
End Function<br />
<br />
Private Function calc_op(ByVal op As mcSymbol, ByVal operand1 As Double, Optional ByVal operand2 As Double = Nothing) As Double<br />
<br />
<br />
Select Case op.Token.ToLower<br />
<br />
Case "&" ' sample to show addition of custom operator<br />
Return 5<br />
<br />
Case "^"<br />
Return (operand1 ^ operand2)<br />
<br />
Case "+"<br />
<br />
Select Case op.PrecedenceLevel<br />
Case PRECEDENCE.LEVEL1<br />
Return (operand2 + operand1)<br />
Case PRECEDENCE.LEVEL4<br />
Return operand1<br />
End Select<br />
<br />
Case "-"<br />
Select Case op.PrecedenceLevel<br />
Case PRECEDENCE.LEVEL1<br />
Return (operand1 - operand2)<br />
Case PRECEDENCE.LEVEL4<br />
Return -1 * operand1<br />
End Select<br />
<br />
<br />
Case "*"<br />
Return (operand2 * operand1)<br />
<br />
Case "/"<br />
Return (operand1 / operand2)<br />
<br />
Case "\"<br />
Return (CLng(operand1) \ CLng(operand2))<br />
<br />
Case "%"<br />
Return (operand1 Mod operand2)<br />
<br />
Case "!"<br />
Dim i As Integer<br />
Dim res As Double = 1<br />
<br />
If operand1 > 1 Then<br />
For i = CInt(operand1) To 1 Step -1<br />
res = res * i<br />
Next<br />
<br />
End If<br />
Return (res)<br />
<br />
End Select<br />
<br />
End Function<br />
<br />
Private Function calc_function(ByVal func As String, ByVal args As Collection) As Double<br />
<br />
Select Case func.ToLower<br />
<br />
Case "cos"<br />
Return (Math.Cos(CDbl(args(1))))<br />
<br />
Case "sin"<br />
Return (Math.Sin(CDbl(args(1))))<br />
<br />
Case "tan"<br />
Return (Math.Tan(CDbl(args(1))))<br />
<br />
Case "floor"<br />
Return (Math.Floor(CDbl(args(1))))<br />
<br />
Case "ceiling"<br />
Return (Math.Ceiling(CDbl(args(1))))<br />
<br />
Case "max"<br />
Return (Math.Max(CDbl(args(1)), CDbl(args(2))))<br />
<br />
Case "min"<br />
Return (Math.Min(CDbl(args(1)), CDbl(args(2))))<br />
<br />
Case "arcsin"<br />
Return (Math.Asin(CDbl(args(1))))<br />
<br />
<br />
Case "arccos"<br />
Return (Math.Acos(CDbl(args(1))))<br />
<br />
Case "arctan"<br />
Return (Math.Atan(CDbl(args(1))))<br />
<br />
<br />
Case "sqrt"<br />
Return (Math.Sqrt(CDbl(args(1))))<br />
<br />
Case "log"<br />
Return (Math.Log10(CDbl(args(1))))<br />
<br />
<br />
Case "log10"<br />
Return (Math.Log10(CDbl(args(1))))<br />
<br />
<br />
Case "abs"<br />
Return (Math.Abs(CDbl(args(1))))<br />
<br />
<br />
Case "round"<br />
Return (Math.Round(CDbl(args(1))))<br />
<br />
Case "ln"<br />
Return (Math.Log(CDbl(args(1))))<br />
<br />
Case "exp"<br />
Return (Math.Exp(CDbl(args(1))))<br />
<br />
Case "neg"<br />
Return (-1 * CDbl(args(1)))<br />
<br />
Case "pos"<br />
Return (+1 * CDbl(args(1)))<br />
<br />
End Select<br />
<br />
End Function<br />
<br />
Private Function identifier(ByVal token As String) As Double<br />
<br />
Select Case token.ToLower<br />
<br />
Case "e"<br />
Return Math.E<br />
Case "pi"<br />
Return Math.PI<br />
Case Else<br />
' look in symbol table....?<br />
End Select<br />
End Function<br />
<br />
Private Function is_operator(ByVal token As String, Optional ByVal level As PRECEDENCE = CType(-1, PRECEDENCE), Optional ByRef operator As mcSymbol = Nothing) As Boolean<br />
<br />
Try<br />
Dim op As New mcSymbol()<br />
op.Token = token<br />
op.PrecedenceLevel = level<br />
op.tag = "test"<br />
<br />
Dim ir As Integer = m_operators.BinarySearch(op, op)<br />
<br />
If ir > -1 Then<br />
<br />
operator = CType(m_operators(ir), mcSymbol)<br />
Return True<br />
End If<br />
<br />
Return False<br />
<br />
Catch<br />
Return False<br />
End Try<br />
End Function<br />
<br />
Private Function is_function(ByVal token As String) As Boolean<br />
<br />
Try<br />
Dim lr As Integer = Array.BinarySearch(m_funcs, token.ToLower)<br />
<br />
Return (lr > -1)<br />
<br />
Catch<br />
Return False<br />
End Try<br />
<br />
End Function<br />
<br />
<br />
Public Function calc_scan(ByVal line As String, ByRef symbols As Queue) As Boolean<br />
<br />
Dim sp As Integer ' start position marker<br />
Dim cp As Integer ' current position marker<br />
Dim col As Integer ' input column<br />
Dim lex_state As Integer<br />
Dim cls As TOKENCLASS<br />
Dim cc As Char<br />
Dim token As String<br />
<br />
symbols = New Queue()<br />
<br />
line = line & " " ' add a space as an end marker<br />
<br />
sp = 0<br />
cp = 0<br />
lex_state = 1<br />
<br />
<br />
Do While cp <= line.Length - 1<br />
<br />
cc = line.Chars(cp)<br />
<br />
' if cc is not found then IndexOf returns -1 giving col = 2.<br />
col = m_colstring.IndexOf(cc) + 3<br />
<br />
' set the input column <br />
Select Case col<br />
<br />
Case 2 ' cc wasn't found in the column string<br />
<br />
If ALPHA.IndexOf(Char.ToUpper(cc)) > 0 Then ' letter column?<br />
col = 1<br />
ElseIf DIGITS.IndexOf(Char.ToUpper(cc)) > 0 Then ' number column?<br />
col = 2<br />
Else ' everything else is assigned to the punctuation column<br />
'col = 6<br />
Throw New System.Exception("Invalid character in expression '" & cc & "'")<br />
End If<br />
<br />
Case Is > 5 ' cc was found and is > 5 so must be in operator column<br />
col = 7<br />
<br />
' case else ' cc was found - col contains the correct column<br />
<br />
End Select<br />
<br />
' find the new state based on current state and column (determined by input)<br />
lex_state = m_State(lex_state - 1, col - 1)<br />
<br />
Select Case lex_state<br />
<br />
Case 3 ' function or variable end state <br />
<br />
' TODO variables aren't supported but substitution <br />
' could easily be performed here or after<br />
' tokenization<br />
<br />
Dim sym As New mcSymbol()<br />
<br />
sym.Token = line.Substring(sp, cp - sp)<br />
If is_function(sym.Token) Then<br />
sym.Cls = TOKENCLASS.KEYWORD<br />
Else<br />
sym.Cls = TOKENCLASS.IDENTIFIER<br />
End If<br />
<br />
symbols.Enqueue(sym)<br />
<br />
lex_state = 1<br />
cp = cp - 1<br />
<br />
Case 5 ' number end state<br />
Dim sym As New mcSymbol()<br />
<br />
sym.Token = line.Substring(sp, cp - sp)<br />
sym.Cls = TOKENCLASS.NUMBER<br />
<br />
symbols.Enqueue(sym)<br />
<br />
lex_state = 1<br />
cp = cp - 1<br />
<br />
Case 6 ' punctuation end state<br />
Dim sym As New mcSymbol()<br />
<br />
sym.Token = line.Substring(sp, cp - sp + 1)<br />
sym.Cls = TOKENCLASS.PUNCTUATION<br />
<br />
symbols.Enqueue(sym)<br />
<br />
lex_state = 1<br />
<br />
Case 7 ' operator end state<br />
<br />
Dim sym As New mcSymbol()<br />
<br />
sym.Token = line.Substring(sp, cp - sp + 1)<br />
sym.Cls = TOKENCLASS.OPERATOR<br />
<br />
symbols.Enqueue(sym)<br />
<br />
lex_state = 1<br />
<br />
End Select<br />
<br />
cp += 1<br />
If lex_state = 1 Then sp = cp<br />
<br />
Loop<br />
<br />
Return True<br />
<br />
End Function<br />
<br />
Private Sub init()<br />
<br />
Dim op As mcSymbol<br />
<br />
Dim state(,) As Integer = {{2, 4, 1, 1, 4, 6, 7}, _<br />
{2, 2, 3, 3, 3, 3, 3}, _<br />
{1, 1, 1, 1, 1, 1, 1}, _<br />
{2, 4, 5, 5, 4, 5, 5}, _<br />
{1, 1, 1, 1, 1, 1, 1}, _<br />
{1, 1, 1, 1, 1, 1, 1}, _<br />
{1, 1, 1, 1, 1, 1, 1}}<br />
<br />
Thread.CurrentThread.CurrentCulture = New CultureInfo("nl-BE")<br />
'Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")<br />
<br />
init_operators()<br />
<br />
m_State = state<br />
'm_colstring = Chr(9) & " " & ".()"<br />
m_colstring = Chr(9) + " " + System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator() + "()"<br />
For Each op In m_operators<br />
m_colstring = m_colstring & op.Token<br />
Next<br />
<br />
Array.Sort(m_funcs)<br />
m_tokens = New Collection()<br />
<br />
End Sub<br />
<br />
<br />
Public Sub New()<br />
<br />
init()<br />
<br />
End Sub<br />
<br />
#Region "Recusrsive Descent Parsing Functions"<br />
<br />
<br />
<br />
Private Function level0(ByRef tokens As Queue) As Double<br />
<br />
Return level1(tokens)<br />
<br />
End Function<br />
<br />
<br />
Private Function level1_prime(ByRef tokens As Queue, ByVal result As Double) As Double<br />
<br />
Dim symbol, operator As mcSymbol<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Return result<br />
End If<br />
<br />
' binary level1 precedence operators....+, -<br />
If is_operator(symbol.Token, PRECEDENCE.LEVEL1, operator) Then<br />
<br />
tokens.Dequeue()<br />
result = calc_op(operator, result, level2(tokens))<br />
result = level1_prime(tokens, result)<br />
<br />
End If<br />
<br />
<br />
Return result<br />
<br />
End Function<br />
<br />
Private Function level1(ByRef tokens As Queue) As Double<br />
<br />
Return level1_prime(tokens, level2(tokens))<br />
<br />
End Function<br />
<br />
Private Function level2(ByRef tokens As Queue) As Double<br />
<br />
Return level2_prime(tokens, level3(tokens))<br />
End Function<br />
<br />
Private Function level2_prime(ByRef tokens As Queue, ByVal result As Double) As Double<br />
<br />
Dim symbol, operator As mcSymbol<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Return result<br />
End If<br />
<br />
' binary level2 precedence operators....*, /, \, %<br />
<br />
If is_operator(symbol.Token, PRECEDENCE.LEVEL2, operator) Then<br />
<br />
tokens.Dequeue()<br />
result = calc_op(operator, result, level3(tokens))<br />
result = level2_prime(tokens, result)<br />
<br />
End If<br />
<br />
Return result<br />
<br />
End Function<br />
<br />
Private Function level3(ByRef tokens As Queue) As Double<br />
<br />
Return level3_prime(tokens, level4(tokens))<br />
<br />
End Function<br />
<br />
Private Function level3_prime(ByRef tokens As Queue, ByVal result As Double) As Double<br />
<br />
Dim symbol, operator As mcSymbol<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Return result<br />
End If<br />
<br />
' binary level3 precedence operators....^<br />
<br />
If is_operator(symbol.Token, PRECEDENCE.LEVEL3, operator) Then<br />
<br />
tokens.Dequeue()<br />
result = calc_op(operator, result, level4(tokens))<br />
result = level3_prime(tokens, result)<br />
<br />
End If<br />
<br />
<br />
Return result<br />
<br />
End Function<br />
<br />
Private Function level4(ByRef tokens As Queue) As Double<br />
<br />
Return level4_prime(tokens)<br />
End Function<br />
<br />
Private Function level4_prime(ByRef tokens As Queue) As Double<br />
<br />
Dim symbol, operator As mcSymbol<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Throw New System.Exception("Invalid expression.")<br />
End If<br />
<br />
' unary level4 precedence right associative operators.... +, -<br />
<br />
If is_operator(symbol.Token, PRECEDENCE.LEVEL4, operator) Then<br />
<br />
tokens.Dequeue()<br />
Return calc_op(operator, level5(tokens))<br />
Else<br />
Return level5(tokens)<br />
End If<br />
<br />
<br />
End Function<br />
<br />
Private Function level5(ByVal tokens As Queue) As Double<br />
<br />
Return level5_prime(tokens, level6(tokens))<br />
<br />
End Function<br />
<br />
Private Function level5_prime(ByVal tokens As Queue, ByVal result As Double) As Double<br />
<br />
Dim symbol, operator As mcSymbol<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Return result<br />
End If<br />
<br />
' unary level5 precedence left associative operators.... !<br />
<br />
If is_operator(symbol.Token, PRECEDENCE.LEVEL5, operator) Then<br />
<br />
tokens.Dequeue()<br />
Return calc_op(operator, result)<br />
<br />
Else<br />
Return result<br />
End If<br />
<br />
End Function<br />
<br />
Private Function level6(ByRef tokens As Queue) As Double<br />
<br />
Dim symbol As mcSymbol<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Throw New System.Exception("Invalid expression.")<br />
Return 0<br />
End If<br />
<br />
Dim val As Double<br />
<br />
<br />
' constants, identifiers, keywords, -> expressions<br />
If symbol.Token = "(" Then ' opening paren of new expression<br />
<br />
tokens.Dequeue()<br />
val = level0(tokens)<br />
<br />
symbol = CType(tokens.Dequeue, mcSymbol)<br />
' closing paren<br />
If symbol.Token <> ")" Then Throw New System.Exception("Invalid expression.")<br />
<br />
Return val<br />
Else<br />
<br />
Select Case symbol.Cls<br />
<br />
Case TOKENCLASS.IDENTIFIER<br />
tokens.Dequeue()<br />
Return identifier(symbol.Token)<br />
<br />
Case TOKENCLASS.KEYWORD<br />
tokens.Dequeue()<br />
Return calc_function(symbol.Token, arguments(tokens))<br />
Case TOKENCLASS.NUMBER<br />
<br />
tokens.Dequeue()<br />
m_stack.Push(CDbl(symbol.Token))<br />
Return CDbl(symbol.Token)<br />
<br />
Case Else<br />
Throw New System.Exception("Invalid expression.")<br />
End Select<br />
End If<br />
<br />
<br />
End Function<br />
<br />
Private Function arguments(ByVal tokens As Queue) As Collection<br />
<br />
Dim symbol As mcSymbol<br />
Dim args As New Collection()<br />
<br />
If tokens.Count > 0 Then<br />
symbol = CType(tokens.Peek, mcSymbol)<br />
Else<br />
Throw New System.Exception("Invalid expression.")<br />
Return Nothing<br />
End If<br />
<br />
Dim val As Double<br />
<br />
If symbol.Token = "(" Then<br />
<br />
tokens.Dequeue()<br />
args.Add(level0(tokens))<br />
<br />
symbol = CType(tokens.Dequeue, mcSymbol)<br />
Do While symbol.Token <> ")" <br />
<br />
If symbol.Token = "," Then<br />
args.Add(level0(tokens))<br />
Else<br />
Throw New System.Exception("Invalid expression.")<br />
Return Nothing<br />
End If<br />
symbol = CType(tokens.Dequeue, mcSymbol)<br />
Loop<br />
<br />
Return args<br />
Else<br />
Throw New System.Exception("Invalid expression.")<br />
Return Nothing<br />
End If<br />
<br />
End Function<br />
<br />
#End Region<br />
<br />
<br />
End Class<br />
Test Code (cmdEvaluate_Click)
<br />
Private Sub cmdEvaluate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdEvaluate.Click<br />
<br />
Dim calc As New mcCalc()<br />
Try<br />
MsgBox("The answer is " & calc.evaluate(txtExpression.Text), , "Expression Evaluation Test")<br />
<br />
Catch ex As Exception<br />
MsgBox("Error " & ex.Message, , "Expression Evaluation Error")<br />
<br />
End Try<br />
<br />
End Sub<br />
Peter Verijke
|
|
|
|
|
I am trying to extend your parser to give expressions in simplesest exact form in addition to an approximate value. For instance, I want it to give the string "pi" for arcsin(sin(sqrt(ln(e^pi))^2)), and e^pi for arcsin(sin(sqrt(e^pi)^2)). Is there any simple way to do this with lexical scanning. My code is extremely complicated (and unwieldly and large), probobly buggy, and I am stuck on a portion of it. I was wonerering if there was an eaiser way to do this. I also wrote in support for complex numbers (this worked).
|
|
|
|
|
I was wondering if it is possible to add support for funtions such as summation to the parser (without rewriting much of the code). For example, (for summation) it would evaluate sum(x,1,5,e+pi+x) by taking the sum of e+pi+x for every integral value of x from 1 to 5.
|
|
|
|
|
In order to work with formulas like
Dim Area As String = "Width*Length"
I wrote an extension to replace the words by numbers before evalutating them:
Private Variables As ArrayList 'place the line at the beginning of the class
Public Sub New()
Me.Variables = New ArrayList 'this line is the addition
init()
End Sub
'this function is used to define variables
Public Function AddVariable(ByVal Name As String, ByVal Value As Double)
Dim vp As New ValuePair(Name, Value)
Me.Variables.Add(vp)
End Function
'some additions before the evaluating gets started
Public Function evaluate(ByVal expression As String) As Double
'first replace all defined variables by their value
For i = 0 To Me.Variables.Count - 1
If Expression.IndexOf(Me.Variables.Item(i).Variable) > 0 Then
Expression = Expression.Replace(Me.Variables.Item(i).Variable, Me.Variables.Item(i).Value)
End If
Next
'... the rest stayed the same
End Function
'a new class (place it after the mccalc-class)
Public Class ValuePair
Public Variable As String
Public Value As Double
Public Sub New(ByVal Variable As String, ByVal Value As Double)
Me.Variable = Variable
Me.Value = Value
End Sub
End Class
There's only one problem:
if you use two variables, which begin with the same letters, you have
to add the longer one first!!
Otherwise you could encounter some trouble.
Example:
Original: "Ship+ShipmentCharge"
Formula1.AddVariable("Ship", 4)
Formula1.AddVariable("ShipmentCharge", 35)
Replaced: "4+4mentCharge"
Another tiny thing I have to mention is, that it can be up to three times
faster to write the replace-lines "by hand":
Dim Calculation As String = "Width*Length"
Calculation = Calculation.Replace("Width", 4)
Calculation = Calculation.Replace("Length", 12)
instead of
Formula.AddVariable(Width, 4)
Formula.AddVariable(Length, 12)
The manual replacement reduces the function calls inside the class to
a minimum. But for calculations below hundred formulas it doesn't matter.
I hope I could help some people with the code
I enjoyed the expression evaluator very much!
I compared it to my own CODEDOM-Evaluator and
the speed of mccalc is about 10 to 1000 times faster!!
|
|
|
|
|