Android Question Unhandled error B4XEval

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Just trying this out:

And trying to make it so that it will handle any expression, so also faulty expressions.
For now I am just testing with this simple but faulty expression:

6/2(2+1)

Added lots of Try Catch and made most return values of type Object (so it can return "error"), but sofar no success
in making Eval return eg "error". I managed to make it return "error" but that was only in debug mode.

Any suggestions how this should be done?

RBS
 

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Just trying this out:

And trying to make it so that it will handle any expression, so also faulty expressions.
For now I am just testing with this simple but faulty expression:

6/2(2+1)

Added lots of Try Catch and made most return values of type Object (so it can return "error"), but sofar no success
in making Eval return eg "error". I managed to make it return "error" but that was only in debug mode.

Any suggestions how this should be done?

RBS
Fixed this now, at least for the mentioned test expression.
This is the text of the class B4XEval:

B4X:
'version 2.01
#Event: Function(Name As String, Values As List) As Double
Sub Class_Globals
    Private Const NUMBER_TYPE = 1, OPERATOR_TYPE = 2 As Int
    Type ParsedNode (Operator As String, Left As ParsedNode, Right As ParsedNode, _
        NodeType As Int, Value As Double)
    Type OrderData (Index As Int, Level As Int, Added As Int)
    Private root As ParsedNode
    Private ParseIndex As Int
    Private Nodes As List
    Private OperatorLevel As Map
    Public Error As Boolean
    Private mCallback As Object
    Private mEventName As String
End Sub

Public Sub Initialize (Callback As Object, EventName As String)
    
    mCallback = Callback
    mEventName = EventName
    OperatorLevel = CreateMap("+": 1, "-": 1, "*":2, "/": 2)
    
End Sub

Public Sub Eval(Expression As String) As Object
    
    Dim d As Double
    
    Error = False
    Expression = Expression.Replace(" ", "").ToLowerCase.Replace("-(", "-1*(")
    d = EvalHelper(Expression)
    
    If Error Then
        Return "error"
    Else
        Return d
    End If
    
End Sub

Private Sub PrepareExpression(expr As String) As String
    
    If Error Then Return "error"
    
    Dim m As Matcher = Regex.Matcher("(\w*)\(", expr)
    Dim sb As StringBuilder
    Dim lastMatchEnd As Int = 0
    
    sb.Initialize
    
    Do While m.Find
        Dim currentStart As Int = m.GetStart(0)
        If currentStart < lastMatchEnd Then Continue
        sb.Append(expr.SubString2(lastMatchEnd, currentStart))
        Dim functionCall As Boolean
        Dim args As List
        If m.Match.Length > 1 Then
            args.Initialize
            functionCall = True
        End If
        Dim level As Int
        Dim start As Int = m.GetEnd(0)
        For i = start To expr.Length - 1
            If expr.CharAt(i) = "(" Then
                level = level + 1
            Else if expr.CharAt(i) = "," And level = 0 Then
                args.Add(CalcSubExpression(expr.SubString2(start, i)))
                start = i + 1
            Else if expr.CharAt(i) = ")" Then
                level = level - 1
                If level = -1 Then
                    Dim d As Double = CalcSubExpression(expr.SubString2(start, i))
                    If functionCall Then
                        args.Add(d)
                        Try
                            d = CallSub3(mCallback, mEventName & "_Function", m.Match.SubString2(0, m.Match.Length - 1), args)
                        Catch
                            Error = True
                            Return "error"
                        End Try
                    End If
                    sb.Append(NumberFormat2(d, 1, 15, 0, False))
                    lastMatchEnd = i + 1
                    Exit
                End If
            End If
        Next
    Loop
    
    If lastMatchEnd < expr.Length Then sb.Append(expr.SubString(lastMatchEnd))
    Return sb.ToString
    
End Sub

Private Sub CalcSubExpression (expr As String) As Double
    
    If Error Then Return 0
    
    Dim be As B4XEval
    be.Initialize (mCallback, mEventName)
    Dim d As Double = be.EvalHelper(expr)
    
    If be.Error Then
        Error = True
        Return 0
    End If
    
    Return d
    
End Sub

'only evaluates numbers and operators. No functions or parenthesis here.
Private Sub EvalHelper (expr As String) As Double
    
    If Error Then Return 0
    
    ParseIndex = 0
    Dim root As ParsedNode
    root.Initialize
    expr = PrepareExpression(expr)
    'If Error Then Return 0
    Dim m As Matcher = Regex.Matcher("[\.\d]+", expr)
    Nodes.Initialize
    Dim lastIndex As Int = 0
    Dim currentOrderData As OrderData
    
    currentOrderData.Initialize
    Nodes.Add(CreateOperatorNode("("))
    
    Do While m.Find
        Dim Operator As String = expr.SubString2(lastIndex, m.GetStart(0))
        Dim rawNumber As String = m.Match
        If Operator.EndsWith("-") Then
            Dim lastNode As ParsedNode = Nodes.Get(Nodes.Size - 1)
            If lastNode.Operator = "(" Or Operator.Length > 1 Then 
            'handle negative numbers: (-2 + 1, 1/-2
                Operator = Operator.SubString2(0, Operator.Length - 1)
                rawNumber = "-" & rawNumber
            End If
        End If
        lastIndex = m.GetEnd(0)
        If Operator.Length > 0 Then
            Dim level As Int = OperatorLevel.Get(Operator)
            If currentOrderData.Level > 0 Then
                If currentOrderData.Level < level Then
                    Nodes.InsertAt(currentOrderData.Index, CreateOperatorNode("("))
                    currentOrderData.Added = currentOrderData.Added + 1
                Else if currentOrderData.Level > level Then
                    If currentOrderData.Added > 0 Then
                        Nodes.Add(CreateOperatorNode(")"))
                        currentOrderData.Added = currentOrderData.Added - 1 
                    End If
                End If
            End If
            currentOrderData.Level = level
            currentOrderData.Index = Nodes.Size + 1
            Nodes.Add(CreateOperatorNode(Operator))
        End If
        Dim d As Double = rawNumber
        Nodes.Add(CreateNumberNode(d))
    Loop
    
    For i = 1 To currentOrderData.Added
        Nodes.Add(CreateOperatorNode(")"))
    Next
    
    Nodes.Add(CreateOperatorNode(")"))
    root = BuildTree
    
    Return EvalNode(root)
    
End Sub

private Sub BuildTree As ParsedNode
    
    If Error Then Return Null
    
    Dim rt As ParsedNode
    
    Do While ParseIndex < Nodes.Size
        Dim pn As ParsedNode = TakeNextNode
        Dim built As Boolean
        If pn.Operator = ")" Then 
            Exit
        Else If pn.Operator = "(" Then 
            pn = BuildTree
            built = True
        End If
        If pn.NodeType = NUMBER_TYPE Or built Then
            If rt.IsInitialized Then
                rt.Right = pn
            Else
                rt = pn
            End If
        Else if pn.NodeType = OPERATOR_TYPE Then
            pn.Left = rt
            rt = pn
        End If
    Loop
    
    If rt.IsInitialized = False Then rt = pn
    
    Return rt
    
End Sub

Private Sub EvalNode (pn As ParsedNode) As Double
    
    If Error Then Return 0
    
    If pn.NodeType = NUMBER_TYPE Then Return pn.Value
    Dim left As Double = EvalNode(pn.Left)
    Dim right As Double = EvalNode(pn.Right)
    
    Select pn.Operator
        Case "+"
            Return left + right
        Case "-"
            Return left - right
        Case "*"
            Return left * right
        Case "/"
            Return left / right
        Case Else
            Log("Syntax error: " & pn.Operator)
            Error = True
            Return "error"
    End Select
    
End Sub

private Sub TakeNextNode As ParsedNode
    
    If Error Then Return Null
    
    Dim pn As ParsedNode = Nodes.Get(ParseIndex)
    ParseIndex = ParseIndex + 1
    
    Return pn
    
End Sub

Private Sub CreateOperatorNode(operator As String) As ParsedNode
    
    If Error Then Return Null
    
    Dim pn As ParsedNode
    pn.Initialize
    pn.NodeType = OPERATOR_TYPE
    pn.Operator = operator
    
    Return pn
    
End Sub

Private Sub CreateNumberNode (d As Double) As ParsedNode
    
    If Error Then Return Null
    
    Dim pn As ParsedNode
    
    pn.Initialize
    pn.NodeType = NUMBER_TYPE
    pn.Value = d
    
    Return pn
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Fixed this now, at least for the mentioned test expression.
This is the text of the class B4XEval:

B4X:
'version 2.01
#Event: Function(Name As String, Values As List) As Double
Sub Class_Globals
    Private Const NUMBER_TYPE = 1, OPERATOR_TYPE = 2 As Int
    Type ParsedNode (Operator As String, Left As ParsedNode, Right As ParsedNode, _
        NodeType As Int, Value As Double)
    Type OrderData (Index As Int, Level As Int, Added As Int)
    Private root As ParsedNode
    Private ParseIndex As Int
    Private Nodes As List
    Private OperatorLevel As Map
    Public Error As Boolean
    Private mCallback As Object
    Private mEventName As String
End Sub

Public Sub Initialize (Callback As Object, EventName As String)
   
    mCallback = Callback
    mEventName = EventName
    OperatorLevel = CreateMap("+": 1, "-": 1, "*":2, "/": 2)
   
End Sub

Public Sub Eval(Expression As String) As Object
   
    Dim d As Double
   
    Error = False
    Expression = Expression.Replace(" ", "").ToLowerCase.Replace("-(", "-1*(")
    d = EvalHelper(Expression)
   
    If Error Then
        Return "error"
    Else
        Return d
    End If
   
End Sub

Private Sub PrepareExpression(expr As String) As String
   
    If Error Then Return "error"
   
    Dim m As Matcher = Regex.Matcher("(\w*)\(", expr)
    Dim sb As StringBuilder
    Dim lastMatchEnd As Int = 0
   
    sb.Initialize
   
    Do While m.Find
        Dim currentStart As Int = m.GetStart(0)
        If currentStart < lastMatchEnd Then Continue
        sb.Append(expr.SubString2(lastMatchEnd, currentStart))
        Dim functionCall As Boolean
        Dim args As List
        If m.Match.Length > 1 Then
            args.Initialize
            functionCall = True
        End If
        Dim level As Int
        Dim start As Int = m.GetEnd(0)
        For i = start To expr.Length - 1
            If expr.CharAt(i) = "(" Then
                level = level + 1
            Else if expr.CharAt(i) = "," And level = 0 Then
                args.Add(CalcSubExpression(expr.SubString2(start, i)))
                start = i + 1
            Else if expr.CharAt(i) = ")" Then
                level = level - 1
                If level = -1 Then
                    Dim d As Double = CalcSubExpression(expr.SubString2(start, i))
                    If functionCall Then
                        args.Add(d)
                        Try
                            d = CallSub3(mCallback, mEventName & "_Function", m.Match.SubString2(0, m.Match.Length - 1), args)
                        Catch
                            Error = True
                            Return "error"
                        End Try
                    End If
                    sb.Append(NumberFormat2(d, 1, 15, 0, False))
                    lastMatchEnd = i + 1
                    Exit
                End If
            End If
        Next
    Loop
   
    If lastMatchEnd < expr.Length Then sb.Append(expr.SubString(lastMatchEnd))
    Return sb.ToString
   
End Sub

Private Sub CalcSubExpression (expr As String) As Double
   
    If Error Then Return 0
   
    Dim be As B4XEval
    be.Initialize (mCallback, mEventName)
    Dim d As Double = be.EvalHelper(expr)
   
    If be.Error Then
        Error = True
        Return 0
    End If
   
    Return d
   
End Sub

'only evaluates numbers and operators. No functions or parenthesis here.
Private Sub EvalHelper (expr As String) As Double
   
    If Error Then Return 0
   
    ParseIndex = 0
    Dim root As ParsedNode
    root.Initialize
    expr = PrepareExpression(expr)
    'If Error Then Return 0
    Dim m As Matcher = Regex.Matcher("[\.\d]+", expr)
    Nodes.Initialize
    Dim lastIndex As Int = 0
    Dim currentOrderData As OrderData
   
    currentOrderData.Initialize
    Nodes.Add(CreateOperatorNode("("))
   
    Do While m.Find
        Dim Operator As String = expr.SubString2(lastIndex, m.GetStart(0))
        Dim rawNumber As String = m.Match
        If Operator.EndsWith("-") Then
            Dim lastNode As ParsedNode = Nodes.Get(Nodes.Size - 1)
            If lastNode.Operator = "(" Or Operator.Length > 1 Then
            'handle negative numbers: (-2 + 1, 1/-2
                Operator = Operator.SubString2(0, Operator.Length - 1)
                rawNumber = "-" & rawNumber
            End If
        End If
        lastIndex = m.GetEnd(0)
        If Operator.Length > 0 Then
            Dim level As Int = OperatorLevel.Get(Operator)
            If currentOrderData.Level > 0 Then
                If currentOrderData.Level < level Then
                    Nodes.InsertAt(currentOrderData.Index, CreateOperatorNode("("))
                    currentOrderData.Added = currentOrderData.Added + 1
                Else if currentOrderData.Level > level Then
                    If currentOrderData.Added > 0 Then
                        Nodes.Add(CreateOperatorNode(")"))
                        currentOrderData.Added = currentOrderData.Added - 1
                    End If
                End If
            End If
            currentOrderData.Level = level
            currentOrderData.Index = Nodes.Size + 1
            Nodes.Add(CreateOperatorNode(Operator))
        End If
        Dim d As Double = rawNumber
        Nodes.Add(CreateNumberNode(d))
    Loop
   
    For i = 1 To currentOrderData.Added
        Nodes.Add(CreateOperatorNode(")"))
    Next
   
    Nodes.Add(CreateOperatorNode(")"))
    root = BuildTree
   
    Return EvalNode(root)
   
End Sub

private Sub BuildTree As ParsedNode
   
    If Error Then Return Null
   
    Dim rt As ParsedNode
   
    Do While ParseIndex < Nodes.Size
        Dim pn As ParsedNode = TakeNextNode
        Dim built As Boolean
        If pn.Operator = ")" Then
            Exit
        Else If pn.Operator = "(" Then
            pn = BuildTree
            built = True
        End If
        If pn.NodeType = NUMBER_TYPE Or built Then
            If rt.IsInitialized Then
                rt.Right = pn
            Else
                rt = pn
            End If
        Else if pn.NodeType = OPERATOR_TYPE Then
            pn.Left = rt
            rt = pn
        End If
    Loop
   
    If rt.IsInitialized = False Then rt = pn
   
    Return rt
   
End Sub

Private Sub EvalNode (pn As ParsedNode) As Double
   
    If Error Then Return 0
   
    If pn.NodeType = NUMBER_TYPE Then Return pn.Value
    Dim left As Double = EvalNode(pn.Left)
    Dim right As Double = EvalNode(pn.Right)
   
    Select pn.Operator
        Case "+"
            Return left + right
        Case "-"
            Return left - right
        Case "*"
            Return left * right
        Case "/"
            Return left / right
        Case Else
            Log("Syntax error: " & pn.Operator)
            Error = True
            Return "error"
    End Select
   
End Sub

private Sub TakeNextNode As ParsedNode
   
    If Error Then Return Null
   
    Dim pn As ParsedNode = Nodes.Get(ParseIndex)
    ParseIndex = ParseIndex + 1
   
    Return pn
   
End Sub

Private Sub CreateOperatorNode(operator As String) As ParsedNode
   
    If Error Then Return Null
   
    Dim pn As ParsedNode
    pn.Initialize
    pn.NodeType = OPERATOR_TYPE
    pn.Operator = operator
   
    Return pn
   
End Sub

Private Sub CreateNumberNode (d As Double) As ParsedNode
   
    If Error Then Return Null
   
    Dim pn As ParsedNode
   
    pn.Initialize
    pn.NodeType = NUMBER_TYPE
    pn.Value = d
   
    Return pn
   
End Sub

RBS
Added some further error handling and I think this may cover all now:

B4X:
'version 2.01
#Event: Function(Name As String, Values As List) As Double
Sub Class_Globals
    Private Const NUMBER_TYPE = 1, OPERATOR_TYPE = 2 As Int
    Type ParsedNode (Operator As String, Left As ParsedNode, Right As ParsedNode, _
        NodeType As Int, Value As Double)
    Type OrderData (Index As Int, Level As Int, Added As Int)
    Private root As ParsedNode
    Private ParseIndex As Int
    Private Nodes As List
    Private OperatorLevel As Map
    Public Error As Boolean
    Private mCallback As Object
    Private mEventName As String
End Sub

Public Sub Initialize (Callback As Object, EventName As String)
    
    mCallback = Callback
    mEventName = EventName
    OperatorLevel = CreateMap("+": 1, "-": 1, "*":2, "/": 2)
    
End Sub

Public Sub Eval(Expression As String) As Object
    
    Dim d As Double
    
    Error = False
    Expression = Expression.Replace(" ", "").ToLowerCase.Replace("-(", "-1*(")
    d = EvalHelper(Expression)
    
    If Error Then
        Return "error"
    Else
        Return d
    End If
    
End Sub

Private Sub PrepareExpression(expr As String) As String
    
    If Error Then Return "error"
    
    Dim m As Matcher = Regex.Matcher("(\w*)\(", expr)
    Dim sb As StringBuilder
    Dim lastMatchEnd As Int = 0
    
    sb.Initialize
    
    Do While m.Find
        Dim currentStart As Int = m.GetStart(0)
        If currentStart < lastMatchEnd Then Continue
        sb.Append(expr.SubString2(lastMatchEnd, currentStart))
        Dim functionCall As Boolean
        Dim args As List
        If m.Match.Length > 1 Then
            args.Initialize
            functionCall = True
        End If
        Dim level As Int
        Dim start As Int = m.GetEnd(0)
        For i = start To expr.Length - 1
            If expr.CharAt(i) = "(" Then
                level = level + 1
            Else if expr.CharAt(i) = "," And level = 0 Then
                args.Add(CalcSubExpression(expr.SubString2(start, i)))
                start = i + 1
            Else if expr.CharAt(i) = ")" Then
                level = level - 1
                If level = -1 Then
                    Dim d As Double = CalcSubExpression(expr.SubString2(start, i))
                    If functionCall Then
                        args.Add(d)
                        Try
                            d = CallSub3(mCallback, mEventName & "_Function", m.Match.SubString2(0, m.Match.Length - 1), args)
                        Catch
                            Error = True
                            Return "error"
                        End Try
                    End If
                    sb.Append(NumberFormat2(d, 1, 15, 0, False))
                    lastMatchEnd = i + 1
                    Exit
                End If
            End If
        Next
    Loop
    
    If lastMatchEnd < expr.Length Then sb.Append(expr.SubString(lastMatchEnd))
    Return sb.ToString
    
End Sub

Private Sub CalcSubExpression (expr As String) As Double
    
    If Error Then Return 0
    
    Dim be As B4XEval
    be.Initialize (mCallback, mEventName)
    Dim d As Double = be.EvalHelper(expr)
    
    If be.Error Then
        Error = True
        Return 0
    End If
    
    Return d
    
End Sub

'only evaluates numbers and operators. No functions or parenthesis here.
Private Sub EvalHelper (expr As String) As Double
    
    If Error Then Return 0
    
    ParseIndex = 0
    Dim root As ParsedNode
    root.Initialize
    expr = PrepareExpression(expr)
    
    If Error Then Return 0
    
    Dim m As Matcher = Regex.Matcher("[\.\d]+", expr)
    Nodes.Initialize
    Dim lastIndex As Int = 0
    Dim currentOrderData As OrderData
    
    currentOrderData.Initialize
    Nodes.Add(CreateOperatorNode("("))
    
    Do While m.Find
        Dim Operator As String = expr.SubString2(lastIndex, m.GetStart(0))
        Dim rawNumber As String = m.Match
        If Operator.EndsWith("-") Then
            Dim lastNode As ParsedNode = Nodes.Get(Nodes.Size - 1)
            If lastNode.Operator = "(" Or Operator.Length > 1 Then
                'handle negative numbers: (-2 + 1, 1/-2
                Operator = Operator.SubString2(0, Operator.Length - 1)
                rawNumber = "-" & rawNumber
            End If
        End If
        lastIndex = m.GetEnd(0)
        If Operator.Length > 0 Then
            Dim level As Int = OperatorLevel.Get(Operator)
            If currentOrderData.Level > 0 Then
                If currentOrderData.Level < level Then
                    Nodes.InsertAt(currentOrderData.Index, CreateOperatorNode("("))
                    currentOrderData.Added = currentOrderData.Added + 1
                Else if currentOrderData.Level > level Then
                    If currentOrderData.Added > 0 Then
                        Nodes.Add(CreateOperatorNode(")"))
                        currentOrderData.Added = currentOrderData.Added - 1
                    End If
                End If
            End If
            currentOrderData.Level = level
            currentOrderData.Index = Nodes.Size + 1
            Nodes.Add(CreateOperatorNode(Operator))
        End If
        Dim d As Double = rawNumber
        Nodes.Add(CreateNumberNode(d))
    Loop
    
    For i = 1 To currentOrderData.Added
        Nodes.Add(CreateOperatorNode(")"))
    Next
    
    Nodes.Add(CreateOperatorNode(")"))
    root = BuildTree
    
    Return EvalNode(root)
    
End Sub

private Sub BuildTree As ParsedNode
    
    If Error Then Return Null
    
    Dim rt As ParsedNode
    
    Do While ParseIndex < Nodes.Size
        Dim pn As ParsedNode = TakeNextNode
        Dim built As Boolean
        If pn.Operator = ")" Then 
            Exit
        Else If pn.Operator = "(" Then 
            pn = BuildTree
            built = True
        End If
        If pn.NodeType = NUMBER_TYPE Or built Then
            If rt.IsInitialized Then
                rt.Right = pn
            Else
                rt = pn
            End If
        Else if pn.NodeType = OPERATOR_TYPE Then
            pn.Left = rt
            rt = pn
        End If
    Loop
    
    If rt.IsInitialized = False Then rt = pn
    
    Return rt
    
End Sub

Private Sub EvalNode (pn As ParsedNode) As Double
    
    If Error Then Return 0
    
    If pn.IsInitialized = False Then
        Error = True
        Return 0
    End If
    
    If pn.Operator = Null Then
        Error = True
        Return 0
    End If
    
    Try
        If pn.NodeType = NUMBER_TYPE Then Return pn.Value
    Catch
        Error = True
        Return 0
    End Try
    
    Dim left As Double = EvalNode(pn.Left)
    Dim right As Double = EvalNode(pn.Right)
    
    Select pn.Operator
        Case "+"
            Return left + right
        Case "-"
            Return left - right
        Case "*"
            Return left * right
        Case "/"
            Return left / right
        Case Else
            Log("Syntax error: " & pn.Operator)
            Error = True
            Return 0
    End Select
    
End Sub

private Sub TakeNextNode As ParsedNode
    
    If Error Then Return Null
    
    Dim pn As ParsedNode = Nodes.Get(ParseIndex)
    ParseIndex = ParseIndex + 1
    
    Return pn
    
End Sub

Private Sub CreateOperatorNode(operator As String) As ParsedNode
    
    If Error Then Return Null
    
    Dim pn As ParsedNode
    pn.Initialize
    pn.NodeType = OPERATOR_TYPE
    pn.Operator = operator
    
    Return pn
    
End Sub

Private Sub CreateNumberNode (d As Double) As ParsedNode
    
    If Error Then Return Null
    
    Dim pn As ParsedNode
    
    pn.Initialize
    pn.NodeType = NUMBER_TYPE
    pn.Value = d
    
    Return pn
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Added some further error handling and I think this may cover all now:

B4X:
'version 2.01
#Event: Function(Name As String, Values As List) As Double
Sub Class_Globals
    Private Const NUMBER_TYPE = 1, OPERATOR_TYPE = 2 As Int
    Type ParsedNode (Operator As String, Left As ParsedNode, Right As ParsedNode, _
        NodeType As Int, Value As Double)
    Type OrderData (Index As Int, Level As Int, Added As Int)
    Private root As ParsedNode
    Private ParseIndex As Int
    Private Nodes As List
    Private OperatorLevel As Map
    Public Error As Boolean
    Private mCallback As Object
    Private mEventName As String
End Sub

Public Sub Initialize (Callback As Object, EventName As String)
   
    mCallback = Callback
    mEventName = EventName
    OperatorLevel = CreateMap("+": 1, "-": 1, "*":2, "/": 2)
   
End Sub

Public Sub Eval(Expression As String) As Object
   
    Dim d As Double
   
    Error = False
    Expression = Expression.Replace(" ", "").ToLowerCase.Replace("-(", "-1*(")
    d = EvalHelper(Expression)
   
    If Error Then
        Return "error"
    Else
        Return d
    End If
   
End Sub

Private Sub PrepareExpression(expr As String) As String
   
    If Error Then Return "error"
   
    Dim m As Matcher = Regex.Matcher("(\w*)\(", expr)
    Dim sb As StringBuilder
    Dim lastMatchEnd As Int = 0
   
    sb.Initialize
   
    Do While m.Find
        Dim currentStart As Int = m.GetStart(0)
        If currentStart < lastMatchEnd Then Continue
        sb.Append(expr.SubString2(lastMatchEnd, currentStart))
        Dim functionCall As Boolean
        Dim args As List
        If m.Match.Length > 1 Then
            args.Initialize
            functionCall = True
        End If
        Dim level As Int
        Dim start As Int = m.GetEnd(0)
        For i = start To expr.Length - 1
            If expr.CharAt(i) = "(" Then
                level = level + 1
            Else if expr.CharAt(i) = "," And level = 0 Then
                args.Add(CalcSubExpression(expr.SubString2(start, i)))
                start = i + 1
            Else if expr.CharAt(i) = ")" Then
                level = level - 1
                If level = -1 Then
                    Dim d As Double = CalcSubExpression(expr.SubString2(start, i))
                    If functionCall Then
                        args.Add(d)
                        Try
                            d = CallSub3(mCallback, mEventName & "_Function", m.Match.SubString2(0, m.Match.Length - 1), args)
                        Catch
                            Error = True
                            Return "error"
                        End Try
                    End If
                    sb.Append(NumberFormat2(d, 1, 15, 0, False))
                    lastMatchEnd = i + 1
                    Exit
                End If
            End If
        Next
    Loop
   
    If lastMatchEnd < expr.Length Then sb.Append(expr.SubString(lastMatchEnd))
    Return sb.ToString
   
End Sub

Private Sub CalcSubExpression (expr As String) As Double
   
    If Error Then Return 0
   
    Dim be As B4XEval
    be.Initialize (mCallback, mEventName)
    Dim d As Double = be.EvalHelper(expr)
   
    If be.Error Then
        Error = True
        Return 0
    End If
   
    Return d
   
End Sub

'only evaluates numbers and operators. No functions or parenthesis here.
Private Sub EvalHelper (expr As String) As Double
   
    If Error Then Return 0
   
    ParseIndex = 0
    Dim root As ParsedNode
    root.Initialize
    expr = PrepareExpression(expr)
   
    If Error Then Return 0
   
    Dim m As Matcher = Regex.Matcher("[\.\d]+", expr)
    Nodes.Initialize
    Dim lastIndex As Int = 0
    Dim currentOrderData As OrderData
   
    currentOrderData.Initialize
    Nodes.Add(CreateOperatorNode("("))
   
    Do While m.Find
        Dim Operator As String = expr.SubString2(lastIndex, m.GetStart(0))
        Dim rawNumber As String = m.Match
        If Operator.EndsWith("-") Then
            Dim lastNode As ParsedNode = Nodes.Get(Nodes.Size - 1)
            If lastNode.Operator = "(" Or Operator.Length > 1 Then
                'handle negative numbers: (-2 + 1, 1/-2
                Operator = Operator.SubString2(0, Operator.Length - 1)
                rawNumber = "-" & rawNumber
            End If
        End If
        lastIndex = m.GetEnd(0)
        If Operator.Length > 0 Then
            Dim level As Int = OperatorLevel.Get(Operator)
            If currentOrderData.Level > 0 Then
                If currentOrderData.Level < level Then
                    Nodes.InsertAt(currentOrderData.Index, CreateOperatorNode("("))
                    currentOrderData.Added = currentOrderData.Added + 1
                Else if currentOrderData.Level > level Then
                    If currentOrderData.Added > 0 Then
                        Nodes.Add(CreateOperatorNode(")"))
                        currentOrderData.Added = currentOrderData.Added - 1
                    End If
                End If
            End If
            currentOrderData.Level = level
            currentOrderData.Index = Nodes.Size + 1
            Nodes.Add(CreateOperatorNode(Operator))
        End If
        Dim d As Double = rawNumber
        Nodes.Add(CreateNumberNode(d))
    Loop
   
    For i = 1 To currentOrderData.Added
        Nodes.Add(CreateOperatorNode(")"))
    Next
   
    Nodes.Add(CreateOperatorNode(")"))
    root = BuildTree
   
    Return EvalNode(root)
   
End Sub

private Sub BuildTree As ParsedNode
   
    If Error Then Return Null
   
    Dim rt As ParsedNode
   
    Do While ParseIndex < Nodes.Size
        Dim pn As ParsedNode = TakeNextNode
        Dim built As Boolean
        If pn.Operator = ")" Then
            Exit
        Else If pn.Operator = "(" Then
            pn = BuildTree
            built = True
        End If
        If pn.NodeType = NUMBER_TYPE Or built Then
            If rt.IsInitialized Then
                rt.Right = pn
            Else
                rt = pn
            End If
        Else if pn.NodeType = OPERATOR_TYPE Then
            pn.Left = rt
            rt = pn
        End If
    Loop
   
    If rt.IsInitialized = False Then rt = pn
   
    Return rt
   
End Sub

Private Sub EvalNode (pn As ParsedNode) As Double
   
    If Error Then Return 0
   
    If pn.IsInitialized = False Then
        Error = True
        Return 0
    End If
   
    If pn.Operator = Null Then
        Error = True
        Return 0
    End If
   
    Try
        If pn.NodeType = NUMBER_TYPE Then Return pn.Value
    Catch
        Error = True
        Return 0
    End Try
   
    Dim left As Double = EvalNode(pn.Left)
    Dim right As Double = EvalNode(pn.Right)
   
    Select pn.Operator
        Case "+"
            Return left + right
        Case "-"
            Return left - right
        Case "*"
            Return left * right
        Case "/"
            Return left / right
        Case Else
            Log("Syntax error: " & pn.Operator)
            Error = True
            Return 0
    End Select
   
End Sub

private Sub TakeNextNode As ParsedNode
   
    If Error Then Return Null
   
    Dim pn As ParsedNode = Nodes.Get(ParseIndex)
    ParseIndex = ParseIndex + 1
   
    Return pn
   
End Sub

Private Sub CreateOperatorNode(operator As String) As ParsedNode
   
    If Error Then Return Null
   
    Dim pn As ParsedNode
    pn.Initialize
    pn.NodeType = OPERATOR_TYPE
    pn.Operator = operator
   
    Return pn
   
End Sub

Private Sub CreateNumberNode (d As Double) As ParsedNode
   
    If Error Then Return Null
   
    Dim pn As ParsedNode
   
    pn.Initialize
    pn.NodeType = NUMBER_TYPE
    pn.Value = d
   
    Return pn
   
End Sub

RBS
Not sure why I got the unhandled errors, but simply altering the Eval Sub to this
(and nil else to be altered from Erel's original code) will sort it all out:

B4X:
'    Type EvalReturn(dValue As Double, _
'                    bError As Boolean)
Public Sub Eval(Expression As String) As EvalReturn
    
    Dim d As Double
    Dim tET As EvalReturn
    
    Error = False
    Expression = Expression.Replace(" ", "").ToLowerCase.Replace("-(", "-1*(")
    d = EvalHelper(Expression)
    
    tET.dValue = d 'will be 0 if error, could return something like 123456 instead to indicate error and avoid the type return
    tET.bError = Error
    
    Return tET
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
Not sure why I got the unhandled errors, but simply altering the Eval Sub to this
(and nil else to be altered from Erel's original code) will sort it all out:

B4X:
'    Type EvalReturn(dValue As Double, _
'                    bError As Boolean)
Public Sub Eval(Expression As String) As EvalReturn
   
    Dim d As Double
    Dim tET As EvalReturn
   
    Error = False
    Expression = Expression.Replace(" ", "").ToLowerCase.Replace("-(", "-1*(")
    d = EvalHelper(Expression)
   
    tET.dValue = d 'will be 0 if error, could return something like 123456 instead to indicate error and avoid the type return
    tET.bError = Error
   
    Return tET
   
End Sub

RBS
There needs to one more change for in case the expression has no numbers, brackets or operators:

B4X:
Private Sub EvalNode (pn As ParsedNode) As Double
    
    If pn.IsInitialized = False Then
        Error = True
        Return 0
    End If
    
    If pn.NodeType = NUMBER_TYPE Then Return pn.Value
    Dim left As Double = EvalNode(pn.Left)
    Dim right As Double = EvalNode(pn.Right)
    Select pn.Operator
        Case "+"
            Return left + right
        Case "-"
            Return left - right
        Case "*"
            Return left * right
        Case "/"
            Return left / right
        Case Else
            Log("Syntax error: " & pn.Operator)
            Error = True
            Return 0
    End Select
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
There needs to one more change for in case the expression has no numbers, brackets or operators:

B4X:
Private Sub EvalNode (pn As ParsedNode) As Double
   
    If pn.IsInitialized = False Then
        Error = True
        Return 0
    End If
   
    If pn.NodeType = NUMBER_TYPE Then Return pn.Value
    Dim left As Double = EvalNode(pn.Left)
    Dim right As Double = EvalNode(pn.Right)
    Select pn.Operator
        Case "+"
            Return left + right
        Case "-"
            Return left - right
        Case "*"
            Return left * right
        Case "/"
            Return left / right
        Case Else
            Log("Syntax error: " & pn.Operator)
            Error = True
            Return 0
    End Select
   
End Sub

RBS
One more alteration needed for in case the provided expression contained only dots:

B4X:
'only evaluates numbers and operators. No functions or parenthesis here.
Private Sub EvalHelper (expr As String) As Double
    
    'Log("Expr: " & expr)
    ParseIndex = 0
    Dim root As ParsedNode
    root.Initialize
    expr = PrepareExpression(expr)
    If Error Then Return 0
    Dim m As Matcher = Regex.Matcher("[\.\d]+", expr)
    Nodes.Initialize
    Dim lastIndex As Int = 0
    Dim currentOrderData As OrderData
    currentOrderData.Initialize
    Nodes.Add(CreateOperatorNode("("))
    
    Do While m.Find
        Dim Operator As String = expr.SubString2(lastIndex, m.GetStart(0))
        Dim rawNumber As String = m.Match
        If Operator.EndsWith("-") Then
            Dim lastNode As ParsedNode = Nodes.Get(Nodes.Size - 1)
            If lastNode.Operator = "(" Or Operator.Length > 1 Then
                'handle negative numbers: (-2 + 1, 1/-2
                Operator = Operator.SubString2(0, Operator.Length - 1)
                rawNumber = "-" & rawNumber
            End If
        End If
        lastIndex = m.GetEnd(0)
        
        If Operator.Length > 0 Then
            Dim level As Int = OperatorLevel.Get(Operator)
            If currentOrderData.Level > 0 Then
                If currentOrderData.Level < level Then
                    Nodes.InsertAt(currentOrderData.Index, CreateOperatorNode("("))
                    currentOrderData.Added = currentOrderData.Added + 1
                Else if currentOrderData.Level > level Then
                    If currentOrderData.Added > 0 Then
                        Nodes.Add(CreateOperatorNode(")"))
                        currentOrderData.Added = currentOrderData.Added - 1
                    End If
                End If
            End If
            currentOrderData.Level = level
            currentOrderData.Index = Nodes.Size + 1
            Nodes.Add(CreateOperatorNode(Operator))
        End If
        
        'Log("rawNumber: " & rawNumber)
        
        'otherwise there will be an unhandled error if provided expression has dots only
        If StringHasCharXOnly(rawNumber, Asc(".")) Then
            Error = True
            Return 0
        End If
        
        Dim d As Double = rawNumber
        Nodes.Add(CreateNumberNode(d))
    Loop
    
    For i = 1 To currentOrderData.Added
        Nodes.Add(CreateOperatorNode(")"))
    Next
    Nodes.Add(CreateOperatorNode(")"))
    root = BuildTree
    
    Return EvalNode(root)
    
End Sub

Sub StringHasCharXOnly(strString As String, iByte As Int) As Boolean
    
    Dim i As Int
    Dim arrBytes() As Byte

    arrBytes = strString.GetBytes("UTF8")

    For i = 0 To arrBytes.Length - 1
        If arrBytes(i) <> iByte Then
            Return False
        End If
    Next
    
    Return True
    
End Sub

RBS
 
Upvote 0

RB Smissaert

Well-Known Member
Licensed User
Longtime User
One more alteration needed for in case the provided expression contained only dots:

B4X:
'only evaluates numbers and operators. No functions or parenthesis here.
Private Sub EvalHelper (expr As String) As Double
   
    'Log("Expr: " & expr)
    ParseIndex = 0
    Dim root As ParsedNode
    root.Initialize
    expr = PrepareExpression(expr)
    If Error Then Return 0
    Dim m As Matcher = Regex.Matcher("[\.\d]+", expr)
    Nodes.Initialize
    Dim lastIndex As Int = 0
    Dim currentOrderData As OrderData
    currentOrderData.Initialize
    Nodes.Add(CreateOperatorNode("("))
   
    Do While m.Find
        Dim Operator As String = expr.SubString2(lastIndex, m.GetStart(0))
        Dim rawNumber As String = m.Match
        If Operator.EndsWith("-") Then
            Dim lastNode As ParsedNode = Nodes.Get(Nodes.Size - 1)
            If lastNode.Operator = "(" Or Operator.Length > 1 Then
                'handle negative numbers: (-2 + 1, 1/-2
                Operator = Operator.SubString2(0, Operator.Length - 1)
                rawNumber = "-" & rawNumber
            End If
        End If
        lastIndex = m.GetEnd(0)
       
        If Operator.Length > 0 Then
            Dim level As Int = OperatorLevel.Get(Operator)
            If currentOrderData.Level > 0 Then
                If currentOrderData.Level < level Then
                    Nodes.InsertAt(currentOrderData.Index, CreateOperatorNode("("))
                    currentOrderData.Added = currentOrderData.Added + 1
                Else if currentOrderData.Level > level Then
                    If currentOrderData.Added > 0 Then
                        Nodes.Add(CreateOperatorNode(")"))
                        currentOrderData.Added = currentOrderData.Added - 1
                    End If
                End If
            End If
            currentOrderData.Level = level
            currentOrderData.Index = Nodes.Size + 1
            Nodes.Add(CreateOperatorNode(Operator))
        End If
       
        'Log("rawNumber: " & rawNumber)
       
        'otherwise there will be an unhandled error if provided expression has dots only
        If StringHasCharXOnly(rawNumber, Asc(".")) Then
            Error = True
            Return 0
        End If
       
        Dim d As Double = rawNumber
        Nodes.Add(CreateNumberNode(d))
    Loop
   
    For i = 1 To currentOrderData.Added
        Nodes.Add(CreateOperatorNode(")"))
    Next
    Nodes.Add(CreateOperatorNode(")"))
    root = BuildTree
   
    Return EvalNode(root)
   
End Sub

Sub StringHasCharXOnly(strString As String, iByte As Int) As Boolean
   
    Dim i As Int
    Dim arrBytes() As Byte

    arrBytes = strString.GetBytes("UTF8")

    For i = 0 To arrBytes.Length - 1
        If arrBytes(i) <> iByte Then
            Return False
        End If
    Next
   
    Return True
   
End Sub

RBS
Found some other expression strings that would cause an unhandled error and fixed these as well.
Attached the altered class.

RBS
 

Attachments

  • B4XEval.bas
    6.5 KB · Views: 137
Upvote 0

Chris2

Active Member
Licensed User
Longtime User
It might be useful to post this in the Libraries & Classes forum, with a usage example perhaps (given that your version is returning a custom type rather than a Double)
 
Last edited:
Upvote 0
Top