VERSION 1.0 CLASS
BEGIN
  MultiUse = -1  'True
  Persistable = 0  'NotPersistable
  DataBindingBehavior = 0  'vbNone
  DataSourceBehavior  = 0  'vbNone
  MTSTransactionMode  = 0  'NotAnMTSObject
END
Attribute VB_Name = "CExpression"
Attribute VB_GlobalNameSpace = False
Attribute VB_Creatable = True
Attribute VB_PredeclaredId = False
Attribute VB_Exposed = False
'--------------------------------------------------------------
'  A class for compiling and evaluating expressions
'  Author:  This code was originally obtained from VBPJ Journal
'           from an article written by Fransisco Balena
'           It was adapted and modified by the author to include
'           additional functions, and improved handling of invalid
'           calculations.
'  Kevin Matney
'  Date:        March 18, 1998
'--------------------------------------------------------------

#Const SupportStrings = -1

Option Explicit

Public Enum expErrorCode
    expOK = 0
    expSyntaxError
    expUnknownFunction
    expUnknownOperator
    expWrongNumberOfArguments
    expInvalidInputforFunction
End Enum

Private Enum expOpcodes
    opUnknown               ' used for errors
    opValue                 ' a constant or a variable
    opStart                 ' special opcodes (operands)
    opOpenBracket
    opMinus                 ' unary opcodes
    opNot
    
    opEnd                   ' special opcodes (operators)
    opComma                 ' DO NOT alter this order!
    opCloseBracket
    
    opFirst_BinaryOperator  ' binary opcodes (symbols)
    opPower = opFirst_BinaryOperator
    opMul
    opDiv                   ' IMPORTANT: these opcodes must be in a
    opIntDiv                ' sequence so that no opcode is a prefix
    opAdd                   ' for another opcode that follows it
    opSub                   ' (e.g. "<>" and "<=" must come before "<"
    opEq                    '  and ">=" must come before ">"
    opNe
    opLe
    opLt
    opGe
    opGt
    opMod                   ' binary opcodes (alphabetic)
    opAnd
    opOr
    opXor
#If SupportStrings Then
    opAppend
#End If

    opFirst_Function        ' opcode of first function
    opPi = opFirst_Function ' zero-argument functions
    opDeg
    opRad
    opAbs                   ' one-argument functions
    opInt
    opFix
    opSgn
    opSqr
    opLog
    opLn
    opExp
    opSin
    opAsin
    opCos
    opAcos
    opTan
    opAtn
    opAtan
    opSec
    opCosec
    opCotan  'add new functions here
    opSin_D
    opCos_D
    opTan_D
    
    opPow
    opMin                   ' two-argument functions
    opMax
    opIIf                   ' three-argument functions
    
#If SupportStrings Then
    opLen                   ' one-argument string functions
    opAsc
    opSpace
    opString                ' two-argument string functions
    opLeft
    opRight
    opMid                   ' three-argument string functions
    opInstr
#End If
    
    opDummy
    opLast_Opcode = opDummy - 1 ' last opcode used
End Enum

' max number of pending operators
Const STACK_SIZE = 30
' max number of items in the expression
Const MAX_ITEMS = 200

' the Default value, returned if a runtime occurs
Public DefaultValue As Variant

' if True (default), runtime errors are raised using the Err.Raise VBA method
' if False, errors are notified to the calling program only through
' the Error* properties
Public RaiseErrors As Boolean

' if True (default), variables are created as needed
' if False, an error occurs if a variable is not declared in advance
Public AutoCreateVariables As Boolean

' member variables
Private m_Expression As String
Private m_ErrorCode As expErrorCode
Private m_ErrorDescription As String
Private m_ErrorPos As Long

' the collection of variables
Private m_Variables As Collection

' the collection of roots
Private m_Roots As Collection

' these arrays hold information on all operands and functions
Dim opNames(opLast_Opcode) As String
Dim opPriority(opLast_Opcode) As Byte
Dim opNumArgs(opLast_Opcode) As Integer

' this holds the expression in compiled form
Dim compItems As Long
Dim compValues() As Variant
Dim compOpcodes() As Integer

' the expression to be evaluated

Property Get Expression() As String
    Expression = m_Expression
End Property

Property Let Expression(ByVal newValue As String)
    m_Expression = newValue
    ' compile the expression
    CompileExpression
End Property

' information on the current error code

Property Get ErrorCode() As expErrorCode
    ErrorCode = m_ErrorCode
End Property

Property Get ErrorDescription() As String
    ErrorDescription = m_ErrorDescription
End Property

Property Get ErrorPos() As Long
    ErrorPos = m_ErrorPos
End Property

' clear the error code

Sub ClearError()
    m_ErrorCode = expOK
    m_ErrorDescription = ""
    m_ErrorPos = 0
End Sub

' access to the variables

Function Variable(varName As Variant, Optional createIfNeeded As Boolean) As CVariable
    On Error Resume Next
    If IsNumeric(varName) Then
        Set Variable = m_Variables(varName)
    Else
        Set Variable = m_Variables(UCase$(varName))
        If Err > 0 And createIfNeeded Then
            Err = 0
            ' if it doesn't exist, create it if requested
            Dim newVar As New CVariable
            newVar.Name = varName
            ' add to the collection of variables
            AddVariable newVar
            Set Variable = newVar
        End If
    End If
End Function

Function AddVariable(newVar As CVariable) As Long
    ' add a new variable to the collection of variables
    ' recognized by this function, returns its index in the collection
    Dim ucaseName As String
    Dim Index As Integer
    
    On Error Resume Next

    ucaseName = UCase$(newVar.Name)
    
    ' add to the collection of variables
    ' this collection is always sorted on variable name
    m_Variables.Remove ucaseName
    Err = 0
    
    For Index = 1 To m_Variables.Count
        If UCase$(m_Variables(Index).Name) > ucaseName Then
            m_Variables.Add newVar, ucaseName, Index
            AddVariable = Index
            Exit Function
        End If
    Next
            
    ' add to the end of the collection
    m_Variables.Add newVar, ucaseName
    AddVariable = m_Variables.Count
            
End Function


Function VariablesCount() As Long
    VariablesCount = m_Variables.Count
End Function





' compile the expression (private)

Private Sub CompileExpression()
    Dim expr As String
    Dim Index As Long
    Dim sp As Integer
    Dim opSp As Integer
    Dim argSp As Integer
    Dim waitForOperator As Boolean
    Dim temp As Variant
    Dim opcode As Integer
    Dim newVar As CVariable
    
    ' reset the compiled expression and the roots
    compItems = 0
    ReDim compOpcodes(MAX_ITEMS) As Integer
    ReDim compValues(MAX_ITEMS) As Variant
    Set m_Roots = New Collection
    
    ' these are the temporary stacks used for parsing
    Dim opStack(STACK_SIZE) As Integer
    Dim argStack(STACK_SIZE) As Integer
    
    ' reset error codes
    m_ErrorCode = expOK
    m_ErrorDescription = ""

    ' add a trailing char to avoid errors and signal the expression end
    expr = m_Expression + opNames(opEnd)
    ' start with the highest priority
    opcode = opStart
    GoSub CompileExprPushOpcode
    Index = 1

    ' main compilation loop

    Do
        SkipBlanks expr, Index
        m_ErrorPos = Index
    
        If waitForOperator = False Then
    
            Select Case Mid$(expr, Index, 1)
            Case "0" To "9", "."
                ' found a numeric constant
                temp = GetNumber(expr, Index)
                If opStack(opSp) = opMinus Then
                    ' if there is an unary minus on the operator stack
                    ' this is a negative number
                    temp = -temp
                    opSp = opSp - 1
                End If
                AppendToCompiled opValue, temp
                sp = sp + 1
                waitForOperator = True
    
#If SupportStrings Then
            Case """", "'"
                ' a string constant
                temp = GetString(expr, Index)
                If m_ErrorCode = expSyntaxError Then GoTo CompileExprSyntaxError
                AppendToCompiled opValue, temp
                sp = sp + 1
                waitForOperator = True
#End If
            Case "+"
                ' unary plus - it is simply skipped over
                Index = Index + 1
    
            Case "-"
                ' unary minus
                opcode = opMinus
                GoSub CompileExprPushOpcode
                Index = Index + 1
    
            Case "("
                opcode = opOpenBracket
                GoSub CompileExprPushOpcode
                Index = Index + 1
    
            Case "A" To "Z", "a" To "z"
                ' this can be the NOT operator, a function name or a variable name
                temp = GetName(expr, Index)
                opcode = FunctionOpcode(UCase$(temp))
                
                If opcode = opNot Then
                    GoSub CompileExprPushOpcode
                ElseIf opcode <> opUnknown Then
                    ' we have found the name of a function
                    If opNumArgs(opcode) = 0 Then
                        ' zero-arg function are very like variables
                        AppendToCompiled opcode
                        sp = sp + 1
                        waitForOperator = True
                        ' zero-arg function may be followed by empty brackets
                        If Mid$(expr, Index, 2) = "()" Then
                            Index = Index + 2
                        End If
                    ElseIf Mid$(expr, Index, 1) = "(" Then
                        ' push the function opcode onto the stack
                        GoSub CompileExprPushOpcode
                        ' push the open bracket onto the stack ...
                        opcode = opOpenBracket
                        GoSub CompileExprPushOpcode
                        Index = Index + 1
                        ' ... but discard the new item added to argStack
                        argSp = argSp - 1
                    Else
                        ' all other functions must be followed by "("
                        m_ErrorDescription = ": missing brackets"
                        GoTo CompileExprSyntaxError
                    End If
                Else
                    ' it must be the name of a variable
                    Set newVar = Variable(temp, AutoCreateVariables)
                    If newVar Is Nothing Then
                        m_ErrorDescription = temp
                        GoTo CompileExprUnknownFunction
                    End If
                    AppendToCompiled opValue, newVar
                    sp = sp + 1
                    waitForOperator = True
                End If
                            
            Case Else
                ' any other character is a syntax error
                If Mid$(expr, Index, 1) = opNames(opEnd) Then
                    m_ErrorDescription = ": unexpected end of expression"
                Else
                    m_ErrorDescription = ": unknown symbol"
                End If
                GoTo CompileExprSyntaxError
    
            End Select
    
        Else
    
            ' we are expecting an operator
    
            ' remember which character comes before this one
            temp = Mid$(expr$, Index - 1, 1)
            ' search the opcode
            opcode = OperatorOpcode(expr, Index)
            If opcode = opUnknown Then GoTo CompileExprUnknownOperator
            
            If opcode = opAnd Or opcode = opOr Or opcode = opXor Or opcode = opMod Then
                ' a few binary operators must be preceeded by a space or a ")"
                ' and must be followed by a space or a "("
                If InStr(" )", temp) = 0 Then
                    GoTo CompileExprSyntaxError
                ElseIf InStr(" (", Mid$(expr$, Index, 1)) = 0 Then
                    GoTo CompileExprSyntaxError
                End If
            End If
    
            ' if it was not a ")" we must prepare to get an operand
            If opcode <> opCloseBracket Then waitForOperator = False
    
            '---------------------------------------------------------------------
            ' this portion of the routine compares the priority of the
            ' operator just parsed with the priority of other operators
            ' pending in opStack(     If opcode = r       = opXor Or opcopt>