Click here to Skip to main content
15,887,596 members
Articles / Programming Languages / Visual Basic
Article

A Math Expression Evaluator

Rate me:
Please Sign up or sign in to vote.
4.87/5 (57 votes)
31 Mar 2003CPOL6 min read 375.4K   4.4K   106   75
Math Expression Evaluator

Image 1

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.

 LetterNumberTabSpace.PunctuationOperator
12411467
22333333
31111111
42455455
51111111
61111111
71111111

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)


Written By
Architect
United States United States
Michael has been developing software for about 19 years primarily in C#, C/C++, Fortran, and Visual Basic. His previous experience includes Internet data services (communication protocols, data storage and GIS) 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.

Comments and Discussions

 
GeneralPraise Pin
veen_rp24-Jun-14 19:54
professionalveen_rp24-Jun-14 19:54 
GeneralMy vote of 5 Pin
amhy19-Apr-11 23:36
amhy19-Apr-11 23:36 
Question7/3,5 = 2,3333333333 and 7/3.5= 0.2 ?????????? Pin
ciapal18-Nov-10 23:20
ciapal18-Nov-10 23:20 
Question9/4.5 = 2,25? Pin
Kakskiv25-Jun-10 3:07
Kakskiv25-Jun-10 3:07 
AnswerRe: 9/4.5 = 2,25? Pin
Cartesio30-Oct-10 0:16
Cartesio30-Oct-10 0:16 
QuestionState table Pin
IamKepu3-Feb-10 10:13
IamKepu3-Feb-10 10:13 
GeneralWrong Result Pin
CodeProject_200814-Oct-08 9:46
CodeProject_200814-Oct-08 9:46 
GeneralRe: Wrong Result Pin
buraksarica25-Apr-11 2:00
buraksarica25-Apr-11 2:00 
QuestionDecimal Problem Pin
saldarius20-Apr-08 8:36
saldarius20-Apr-08 8:36 
AnswerRe: Decimal Problem Pin
Remy Blaettler24-Apr-12 23:01
Remy Blaettler24-Apr-12 23:01 
QuestionJust what I am after can you use the "Enter" key to evaluate ? Pin
gearcam30-Jan-08 10:59
gearcam30-Jan-08 10:59 
AnswerRe: Just what I am after can you use the "Enter" key to evaluate ? Pin
saldarius20-Apr-08 8:38
saldarius20-Apr-08 8:38 
GeneralEquation Evaluator Pin
Anshul R16-Dec-07 23:37
Anshul R16-Dec-07 23:37 
GeneralExtending the parser for evaluating ternary operator Pin
pollirrata8-Jun-07 4:44
pollirrata8-Jun-07 4:44 
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
Questionletter then number transition state Pin
jokiz30-May-07 22:13
jokiz30-May-07 22:13 
Generalproblem with decimal numbers Pin
scalpa9830-Apr-07 5:53
scalpa9830-Apr-07 5:53 
AnswerRe: problem with decimal numbers Pin
Ruprt20-Aug-07 1:39
Ruprt20-Aug-07 1:39 
GeneralRe: problem with decimal numbers Pin
Remy Blaettler24-Apr-12 23:01
Remy Blaettler24-Apr-12 23:01 
GeneralThanks it's very useful Pin
grantmasterb17-Nov-06 7:13
grantmasterb17-Nov-06 7:13 
GeneralRe: Thanks it's very useful Pin
Ruprt19-Aug-07 9:09
Ruprt19-Aug-07 9:09 
Generalbug Pin
scalpa983-Nov-06 9:13
scalpa983-Nov-06 9:13 
GeneralRe: bug Pin
paulhumphris19-Jan-07 4:23
paulhumphris19-Jan-07 4:23 
QuestionRounding The Answer. Pin
mshariq5-Oct-06 0:35
mshariq5-Oct-06 0:35 
GeneralCongratulation Pin
snort11-Sep-06 22:20
snort11-Sep-06 22:20 
QuestionPlease Help, just trying to add an X Pin
Brad Galloway4-Sep-06 15:00
Brad Galloway4-Sep-06 15:00 

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

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