'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