Click here to Skip to main content
Licence CPOL
First Posted 31 Mar 2003
Views 243,451
Bookmarked 93 times

A Math Expression Evaluator

By | 31 Mar 2003 | Article
Math Expression Evaluator

Introduction

A mathematical expression evaluator can be a useful piece of code if you often write applications which rely on any kind of scripting. Graphics applications which create composite images from templates, form filling or spreadsheet software which performs configurable calculations based on user input, or data analysis applications that perform calculations on batch data from scripts are just a few that come to my mind.

Some knowledge of lexical analysis, state machines and parsing would be helpful, but not necessary. The brief discussion here and a little experimentation with the code in the debugger, should hopefully provide adequate explanation to at least get started using the code.

Lexical scanning

The first step in evaluating an expression is to identify the individual components, or tokens, of the expression. This evaluator uses a finite state machine based lexical scanner to identify tokens, assigning each a category such as number, operator, punctuation, or function. A state machine uses a set of predefined rules to make transitions between states, based on the current state and input (characters from a buffer). It will eventually reach an end state (let's hope), at which point, end state specific code can be executed. In this case, an end state signals that a token has been found, and end state specific code identifies it within the input buffer and stores it to a list of tokens.

State machines are typically driven by a table like the one below, which is used in the code presented in this article. The state is indicated on each row, and the column is determined by the input character. The end states are those states in which all entries are 1. When one of these end states is reached, the code, having tracked it's start position and it's current position, cuts out the token from the buffer, stores it to a list with an associated type (number, operator, etc.) and then returns to the start state, state one.

For example, lets start with a buffer of "73 " (a space is added as an end marker). The transition would be as follows: From State 1, an input of 7 (number) indicates a move to state 4. From state 4, an input of 3 (number) indicates staying in state 4. From state 4, an input of ' ' (space) indicates a move to state 5. State 5 is the end state for a number. At this point the number 73 has been identified, which would then be stored in a list of tokens.

  Letter Number Tab Space . Punctuation Operator
1 2 4 1 1 4 6 7
2 2 3 3 3 3 3 3
3 1 1 1 1 1 1 1
4 2 4 5 5 4 5 5
5 1 1 1 1 1 1 1
6 1 1 1 1 1 1 1
7 1 1 1 1 1 1 1

You might have noticed a little cheating on the column marked Operator. Ordinarily, each operator might have its own column, directing the state machine when that operator character is input. However, single character operators can be combined, provided that some special handling to set the column correctly, is added to the code. This was done so that new operators could easily be added without any modification to the state table. More on this later.

Parsing and evaluation

Once a list of tokens has been generated, each assigned an appropriate type (operator, number, etc), the expression is parsed and evaluated. Parsing verifies the syntax of the expression, restructures the expression to account for operator precedence and, in this case, also evaluates the expression. This code uses a relatively simple recursive descent parsing algorithm.

Recursive descent parsing is implemented through a series of recursive functions, each function handling a different portion of the syntax of an expression. The virtue of recursive decent parsing is that, it is easy to implement. The primary drawback though is that, the language of the expression, math in this case, is hard coded into the structure of the code. As a consequence, a change in the language often requires that the code itself be modified. There are standard parsing algorithms driven by tables, rather than functions, but typically require additional software to generate portions of the code and the tables, and can require much greater effort to implement.

However, the recursive descent parser used in this code has been written in a manner that will allow language modifications typical of those in math expressions (functions and operators), with no changes to the structure of the code.

Adding new operators and functions

This code handles most of the basic operators and functions normally encountered in an expression. However, adding support for additional operators and functions can be implemented simply.

The recursive descent parsing functions have been coded in a series of levels, each level handling operators of a particular precedence, associativity (left, or right) and what might be referred to as degree (unary, or binary). There are 3 levels of precedence (level1, level2 and level3) for binary, left associative operators. By default, level1 handles addition and subtraction (+,-), level2 handles multiplicative operands (*, /, %, \) and level three handles exponents (^). Adding a new operator at any of these levels requires 2 steps. One is to modify the init_operators function to include a symbol for the new operator, specifying the precedence level and the character denoting the operation. Only single character operators can be added without additional changes to the lexical scanner. The second step is to modify the calc_op function to handle the operation, which should become clear once in the code. Level4 handles right associative unary operators (-, + i.e. negation, etc.) and level5 handles left associative unary operators (! factorials). The process to add new operators at these levels is the same as above.

The addition of functions is equally simple. The new function name must first be added to the m_funcs array which is initialized in the declarations of the mcCalc class. Then the calc_function function must be modified to perform the function operation. Function arguments are passed to the calc_function function in a collection. The parser simple passes in the number of comma delimited values it finds enclosed in parenthesis, following the function. The calc_function function is responsible for removing the number of arguments required for the function, and generating any errors when an incorrect number of arguments is passed. Variable length argument lists can even be implemented by simply indicating the number of function arguments in the first argument.

Points of interest

There are several interesting modifications to this code that could provide additional utility. Variable identifiers and substitution could also be of use to those needing a more thorough evaluation tool. Support for caller defined functions or operators through the use of delegates would be a nice addition for anyone interested in using this code as an external assembly. There are certainly more, and any suggestions or modifications for such are welcome. Hopefully this code will prove useful to you in it's application or at least in it's explanation of some of the principles behind expression evaluation.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)

About the Author

Michael Combs

Architect

United States United States

Member

Michael has been developing software for about 12 years primarily in Fortran, C/C++, Visual Basic and now .NET. His previous experience includes Internet data services (communication, data storage and mapping) for the mortgage industry, oil platform instrumentation and explosives simulation and testing. He holds a B.S. in astrophysics and computer science. He is currently working for Global Software in Oklahoma City developing law enforcement and emergency services related software.

Sign Up to vote   Poor Excellent
Add a reason or comment to your vote: x
Votes of 3 or less require a comment

Comments and Discussions

 
You must Sign In to use this message board. (secure sign-in)
 
Search this forum  
 FAQ
    Noise  Layout  Per page   
  Refresh
Generalbug Pinmemberscalpa989:13 3 Nov '06  
GeneralRe: bug Pinmemberpaulhumphris4:23 19 Jan '07  
QuestionRounding The Answer. Pinmembermshariq0:35 5 Oct '06  
GeneralCongratulation Pinmembersnort22:20 11 Sep '06  
QuestionPlease Help, just trying to add an X PinmemberBrad Galloway15:00 4 Sep '06  
AnswerRe: Please Help, just trying to add an X [modified] Pinmembernim5210:33 20 Sep '06  
GeneralEXP and Error Handling PinmemberPeter Verijke10:25 7 Jun '06  
Hi,
 
Very nice piece of code. Big Grin | :-D
 
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:

Option Strict On
Imports System.Globalization
Imports System.Threading
 
Public Class mcCalc
 
Private Class mcSymbol
Implements IComparer
 
Public Token As String
Public Cls As mcCalc.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 mcSymbol
asym = CType(x, mcSymbol)
bsym = CType(y, mcSymbol)
 

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", _
"exp", "round", "abs", "neg", "pos"}
 
Private m_operators As ArrayList
 
Private m_stack As New Stack()
 
Private Sub init_operators()
 
Dim op As mcSymbol
 
m_operators = New ArrayList()
 
op = New mcSymbol()
op.Token = "-"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL1
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "+"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL1
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "*"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "/"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "\"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "%"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL2
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "^"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL3
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "!"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL5
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "&"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL5
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "-"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL4
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "+"
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL4
m_operators.Add(op)
 
op = New mcSymbol()
op.Token = "("
op.Cls = TOKENCLASS.OPERATOR
op.PrecedenceLevel = PRECEDENCE.LEVEL5
m_operators.Add(op)
 
op = New mcSymbol()
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 IsNumeric(expression) Then Return CType(expression, Double)
 
calc_scan(expression, symbols)
 
Return level0(symbols)
 
Catch ex As Exception
 
Throw New System.Exception(ex.Message)
 
End Try
 
End Function
 
Private Function calc_op(ByVal op As mcSymbol, 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 "exp"
Return (Math.Exp(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 mcSymbol = Nothing) As Boolean
 
Try
Dim op As New mcSymbol()
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), mcSymbol)
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
Throw New System.Exception("Invalid character in expression '" & cc & "'")
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 mcSymbol()
 
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 mcSymbol()
 
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 mcSymbol()
 
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 mcSymbol()
 
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 mcSymbol
 
Dim state(,) As Integer = {{2, 4, 1, 1, 4, 6, 7}, _
{2, 2, 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}}
 
Thread.CurrentThread.CurrentCulture = New CultureInfo("nl-BE")
'Thread.CurrentThread.CurrentCulture = New CultureInfo("en-US")
 
init_operators()
 
m_State = state
'm_colstring = Chr(9) & " " & ".()"
m_colstring = Chr(9) + " " + System.Globalization.CultureInfo.CurrentCulture.NumberFormat.CurrencyDecimalSeparator() + "()"
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 mcSymbol
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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 mcSymbol
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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 mcSymbol
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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 mcSymbol
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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 mcSymbol
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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 mcSymbol
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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, mcSymbol)
' 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 mcSymbol
Dim args As New Collection()
 
If tokens.Count > 0 Then
symbol = CType(tokens.Peek, mcSymbol)
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, mcSymbol)
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, mcSymbol)
Loop
 
Return args
Else
Throw New System.Exception("Invalid expression.")
Return Nothing
End If
 
End Function
 
#End Region
 

End Class

 
Test Code (cmdEvaluate_Click)

Private Sub cmdEvaluate_Click(ByVal sender As System.Object, ByVal e As System.EventArgs) Handles cmdEvaluate.Click
 
Dim calc As New mcCalc()
Try
MsgBox("The answer is " & calc.evaluate(txtExpression.Text), , "Expression Evaluation Test")
 
Catch ex As Exception
MsgBox("Error " & ex.Message, , "Expression Evaluation Error")
 
End Try
 
End Sub

 
Peter Verijke
QuestionExtending the Parser to Simplify Expressions PinmemberJasonSG10:25 26 Nov '05  
QuestionAdditional Functions PinmemberJasonSG7:48 25 Nov '05  
GeneralCode extension: Formulas with variables inside PinmemberPanzermeyer2:09 18 Aug '05  
GeneralIIf statements... PinmemberJPamental8:39 27 Jul '05  
GeneralRe: IIf statements... Pinmemberjokiz16:54 25 Sep '06  
QuestionWhat should I do if... Pinmemberviettho8:34 4 Jun '04  
GeneralReadability Suggestions PinmemberJimHugh9:55 2 Feb '04  
GeneralNull value Pinmembergadish@log-on.com7:21 1 Feb '04  
GeneralCompact Framework Problem "BinarySearch" PinsussCarter Barnes11:48 13 Jan '04  
GeneralRe: Compact Framework Problem "BinarySearch" PinmemberMichael Combs18:33 14 Jan '04  
GeneralRe: Compact Framework Problem "BinarySearch" PinsussJohn M-W13:54 24 Feb '04  
Generalsteady decline of memory PinsussAnonymous23:11 3 Nov '03  
GeneralRe: steady decline of memory PinsussAnonymous4:07 4 Nov '03  
Generallog10 PinmemberSerena_Sutton3:27 16 Oct '03  
GeneralRe: log10 PinmemberMichael Combs4:23 16 Oct '03  
GeneralC++ PinmemberA M Ginsberg21:56 24 Aug '03  
Questiondecimal numbers? PinsussTheDoc9:49 1 Aug '03  
AnswerRe: decimal numbers? PinmemberMichael Combs9:55 1 Aug '03  

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.

Permalink | Advertise | Privacy | Mobile
Web03 | 2.5.120529.1 | Last Updated 1 Apr 2003
Article Copyright 2003 by Michael Combs
Everything else Copyright © CodeProject, 1999-2012
Terms of Use
Layout: fixed | fluid