Code archives/Algorithms/Term calculator

This code has been declared by its author to be Public Domain code.

Download source code

Term calculator by Noobody2008
As the title suggests, this algorithm calculates the result of a mathematical term passed over in a string.
It's able to do most of the basic stuff like addition, substraction, multiplication, division and can also handle brackets, powers (like 2^5) and boolean comparisons.
You're also able to use variables - just take a look at the example.

In the code, two functions are important:
- TermTokenize( Calc$ ): This Functions splits the given term into single tokens and returns the first token. The returned type is a TTerm, which can later be used in
- TermCalculate( Term.TTerm, Vars$ ) to calculate the result of the term. It's last parameter is optional. If you've used any variables in your term, you must allocate them values in the string.

The description is maybe a bit confusing since my English is not very good, but I hope an example will do better.
Let's say, you have a string that contains "X^(2-Test)+300".
First of all you call TermTokenize and store the returned type for later use: Term.TTerm = TermTokenize( "X^(2-Test)+300" )
Now you can call TermCalculate later in your code and assign values to these variables: Result# = TermCalculate( Term, "X=3,Test=1.5" )

This is useful anywhere the user could type in mathematical expressions, for example script languages or graph plotters.

The code contains the functions used by the algorithm along with a small example (it just draws a bunch of different graphs and displays how long it took).

So far I haven't found any bugs, but if you encounter any, please tell me.
Const GWIDTH = 800
Const GHEIGHT = 600

Graphics 800, 600, 0, 2
SetBuffer BackBuffer()

Graph1.TTerm = TermTokenize( "-(((X-400)*Scale)^2-300)" )
Graph2.TTerm = TermTokenize( "-(1/((X-400)*Scale)-300)" )
Graph3.TTerm = TermTokenize( "-(2^((X-400)*Scale)-300)" )
Graph4.TTerm = TermTokenize( "-100*(X<=(800/2))+300" )

Timer = CreateTimer( 60 )

While Not KeyHit( 1 )
   Cls
   
   Counter = MilliSecs()
   PlotGraph( Graph1, "Scale=0.05", $FFFF0000 )
   PlotGraph( Graph2, "Scale=0.0001", $FF00FF00 )
   PlotGraph( Graph3, "Scale=0.03", $FF0000FF )
   PlotGraph( Graph4, "", $FFFFFF00 )
   
   Text 0, 0, MilliSecs() - Counter
   
   Flip 0
   WaitTimer Timer
Wend
End

Function PlotGraph( Graph.TTerm, VarString$, ARGB )
   LockBuffer BackBuffer()
   
   For X = 0 To GWIDTH - 1
      WritePixel X, TermCalculate( Graph, VarString$ + ",X=" + X ), ARGB
   Next
   
   UnlockBuffer BackBuffer()
End Function

;############################# Everithing beyond this line is used by the algorithm, everything above this line is just the example.

Type TTerm
   Field Action
   Field P#
   Field PVar$
   Field PTerm.TTerm
   
   Field Result#
End Type

Const TERM_INITIATE      = 1
Const TERM_ADD         = 2
Const TERM_SUBSTRACT   = 3
Const TERM_MULTIPLY      = 4
Const TERM_DIVIDE      = 5
Const TERM_GREATER      = 6
Const TERM_SMALLER      = 7
Const TERM_EQUAL      = 8
Const TERM_GREATEREQUAL   = 9
Const TERM_SMALLEREQUAL   = 10
Const TERM_END         = 11

Global ResultTerm.TTerm

Function TermTokenize.TTerm( Calc$ )
   Term.TTerm = New TTerm
      Term\Action = TERM_INITIATE
   
   If Left( Calc$, 1 ) <> "-" Then Calc$ = "+" + Calc$
   
   While Calc$ <> ""
      Term.TTerm = New TTerm
      
      If FirstTerm.TTerm = Null Then FirstTerm = Term
      
      Select Left( Calc$, 1 )
         Case "-"
            Term\Action = TERM_SUBSTRACT
         Case "+"
            Term\Action = TERM_ADD
         Case "*"
            Term\Action = TERM_MULTIPLY
         Case "/"
            Term\Action = TERM_DIVIDE
         Case "^"
            Term\Action = TERM_EXPONENT
         Case ">"
            If Mid( Calc$, 2, 1 ) = "="  Then
               Term\Action = TERM_GREATEREQUAL
               Calc$ = Right( Calc$, Len( Calc$ ) - 1 )
            Else
               Term\Action = TERM_GREATER
            EndIf
         Case "<"
            If Mid( Calc$, 2, 1 ) = "="  Then
               Term\Action = TERM_SMALLEREQUAL
               Calc$ = Right( Calc$, Len( Calc$ ) - 1 )
            Else
               Term\Action = TERM_SMALLER
            EndIf
         Case "="
            Term\Action = TERM_EQUAL
      End Select
      
      Offset = FindOperand( Calc$, 2 )
      If Offset = 0 Then Offset = Len( Calc$ ) + 1
      
      Param$ = Mid( Calc$, 2, Offset - 2 )
      
      If IsLetter( Left( Param$, 1 ) ) Then
         Term\PVar$ = Param$
      ElseIf Left( Param$, 1 ) = "(" Then
         Term\PTerm = TermTokenize( Right( Left( Param$, Len( Param$ ) - 1 ), Len( Param$ ) - 2 ) )
      Else
         Term\P# = Float( Param$ )
      EndIf
      
      DebugLog Left( Calc$, 1 ) + Param$
      
      Calc$ = Right( Calc$, Len( Calc$ ) - Offset + 1 )
   Wend
   
   Term.TTerm = New TTerm
      Term\Action = TERM_END
   
   Return FirstTerm
End Function

Function TermCalculate#( Term.TTerm, Vars$ = "" )
   Local Result# = 0
   
   If Term = Null Then Return 0
   
   While Term\Action <> TERM_END
      If Term\PVar$ <> "" Then
         Offset = Standalone( Lower( Vars$ ), Lower( Term\PVar$ ) )
         
         If Offset Then
            Offset = Offset + Len( Term\PVar$ )
            
            Offset2 = Instr( Vars$, ",", Offset )
            If Offset2 = 0 Then Offset2 = Len( Vars$ )
            
            Term\P# = Float( Mid( Vars$, Offset + 1, Offset2 - Offset ) )
         Else
            RuntimeError "Undefined Variable: '" + Term\PVar$ + "'!
         EndIf
      ElseIf Term\PTerm <> Null
         Term\P# = TermCalculate( Term\PTerm, Vars$ )
      EndIf
      
      Select Term\Action
         Case TERM_ADD
            Result# = Result# + Term\P#
            Term\Result# = Term\P#
         Case TERM_SUBSTRACT
            Result# = Result# - Term\P#
            Term\Result# = -Term\P#
         Case TERM_MULTIPLY
            ParamTerm.TTerm = Before Term
            
            Result# = Result# - ParamTerm\Result#
            Result# = Result# + Term\P#*ParamTerm\Result#
            
            Term\Result# = Term\P#*ParamTerm\Result#
         Case TERM_DIVIDE
            ParamTerm.TTerm = Before Term
            
            Result# = Result# - ParamTerm\Result#
            Result# = Result# + ParamTerm\Result#/Term\P#
            
            Term\Result# = ParamTerm\Result#/Term\P#
         Case TERM_EXPONENT
            ParamTerm.TTerm = Before Term
            
            Result# = Result# - ParamTerm\Result#
            
            Select ParamTerm\Action
               Case TERM_MULTIPLY
                  ParamTerm\Result# = ParamTerm\Result#/ParamTerm\P#
                  If Term\P# = 0 Then
                     Term\Result# = 1
                  ElseIf Float( Int( Term\P# ) ) = Term\P# Then
                     Term\Result# = ParamTerm\Result#
                     For i = 1 To Term\P# - 1
                        Term\Result# = Term\Result#*ParamTerm\Result#
                     Next
                  Else
                     Term\Result# = ParamTerm\Result#^Term\P#
                  EndIf
                  Result# = Result# + Term\Result#*ParamTerm\Result#
               Case TERM_DIVIDE
                  ParamTerm\Result# = ParamTerm\Result#*ParamTerm\P#
                  If Term\P# = 0 Then
                     Term\Result# = 1
                  ElseIf Float( Int( Term\P# ) ) = Term\P# Then
                     Term\Result# = ParamTerm\Result#
                     For i = 1 To Term\P# - 1
                        Term\Result# = Term\Result#*ParamTerm\Result#
                     Next
                  Else
                     Term\Result# = ParamTerm\Result#^Term\P#
                  EndIf
                  Result# = Result# + ParamTerm\Result#/Term\Result#
               Default
                  If Term\P# = 0 Then
                     Term\Result# = 1
                  ElseIf Float( Int( Term\P# ) ) = Term\P# Then
                     Term\Result# = ParamTerm\Result#
                     For i = 1 To Term\P# - 1
                        Term\Result# = Term\Result#*ParamTerm\Result#
                     Next
                  Else
                     Term\Result# = ParamTerm\Result#^Term\P#
                  EndIf
                  Result# = Result# + Term\Result#
            End Select
         Case TERM_GREATER
            ParamTerm.TTerm = Before Term
            
            Result = Result - ParamTerm\Result#
            
            Result# = Result# + ( ParamTerm\Result# > Term\P# )
         Case TERM_EQUAL
            ParamTerm.TTerm = Before Term
            
            Result = Result - ParamTerm\Result#
            
            Result# = Result# + ( ParamTerm\Result# = Term\P# )
         Case TERM_SMALLER
            ParamTerm.TTerm = Before Term
            
            Result = Result - ParamTerm\Result#
            
            Result# = Result# + ( ParamTerm\Result# < Term\P# )
         Case TERM_GREATEREQUAL
            ParamTerm.TTerm = Before Term
            
            Result = Result - ParamTerm\Result#
            
            Result# = Result# + ( ParamTerm\Result# >= Term\P# )
         Case TERM_SMALLEREQUAL
            ParamTerm.TTerm = Before Term
            
            Result = Result - ParamTerm\Result#
            
            Result# = Result# + ( ParamTerm\Result# <= Term\P# )
      End Select
      
      If Term\PTerm <> Null Then Term = After ResultTerm Else Term = After Term
   Wend
   
   Term\Result# = Result#
   ResultTerm = Term
   
   Return Result#
End Function

Function Standalone( SourceString$, SearchString$, Offset = 1 )
   Offset = Instr( SourceString$, SearchString$, Offset )
   
   While Offset
      If Offset > 1 Then LeftEnd$ = Mid( SourceString$, Offset - 1, 1 ) Else LeftEnd$ = ","
      RightEnd$ = Mid( SourceString$, Offset + Len( SearchString$ ), 1 )
      
      If RightEnd$ = "=" And LeftEnd$ = "," Then Return Offset Else Offset = Instr( SourceString$, SearchString$, Offset + 1 )
   Wend
   
   Return False
End Function

Function IsLetter( Char$ )
   If Asc( Char$ ) >= 65 And Asc( Char$ ) <= 90 Then Return True
   If Asc( Char$ ) >= 97 And Asc( Char$ ) <= 122 Then Return True
End Function

Function IsInBrackets( SourceString$, Offset )
   OffsetBracket = Instr( SourceString$, "(" )
   
   While OffsetBracket
      Level = 1
      OffsetOpenBracket = OffsetBracket
      OffsetCloseBracket = 0
      While Level > 0
         OffsetOpenBracket = Instr( SourceString$, "(", OffsetOpenBracket + 1 )
         OffsetCloseBracket = Instr( SourceString$, ")", OffsetCloseBracket + 1 )
         
         If OffsetCloseBracket > 0 And ( OffsetCloseBracket < OffsetOpenBracket Or OffsetOpenBracket = 0 ) Then
            If Level - 1 = 0 Then Exit
         EndIf
         
         If OffsetOpenBracket Then Level = Level + 1
         If OffsetCloseBracket Then Level = Level - 1
      Wend
      
      If Offset > OffsetBracket And Offset < OffsetCloseBracket Then
         If OffsetBracket > 1 Then
            Char$ = Mid( SourceString$, OffsetBracket - 1, 1 )
            If Not IsLetter( Char$ ) Then Return True
         EndIf
         
         Char$ = Mid( SourceString$, OffsetCloseBracket + 1, 1 )
         If Not IsLetter( Char$ ) Then Return True
      EndIf
      
      OffsetBracket = Instr( SourceString$, "(", OffsetBracket + 1 )
   Wend
   
   Return False
End Function

Function FindOperand( SourceString$, Offset = 1 )
   OffsetPlus = Instr( SourceString$, "+", Offset )
   OffsetMinus = Instr( SourceString$, "-", Offset )
   OffsetStar = Instr( SourceString$, "*", Offset )
   OffsetSlash = Instr( SourceString$, "/", Offset )
   OffsetCaret = Instr( SourceString$, "^", Offset )
   OffsetSmaller = Instr( SourceString$, "<", Offset )
   OffsetBigger = Instr( SourceString$, ">", Offset )
   OffsetEqual = Instr( SourceString$, "=", Offset )
   
   If OffsetPlus = 0 Then OffsetPlus = 999999
   If OffsetMinus = 0 Then OffsetMinus = 999999
   If OffsetStar = 0 Then OffsetStar = 999999
   If OffsetSlash = 0 Then OffsetSlash = 999999
   If OffsetCaret = 0 Then OffsetCaret = 999999
   If OffsetSmaller = 0 Then OffsetSmaller = 999999
   If OffsetBigger = 0 Then OffsetBigger = 999999
   If OffsetEqual = 0 Then OffsetEqual = 999999
   
   MinValue = Minimum( OffsetPlus, OffsetMinus )
   MinValue = Minimum( MinValue, OffsetStar )
   MinValue = Minimum( MinValue, OffsetSlash )
   MinValue = Minimum( MinValue, OffsetCaret )
   MinValue = Minimum( MinValue,  OffsetSmaller )
   MinValue = Minimum( MinValue,  OffsetBigger )
   MinValue = Minimum( MinValue,  OffsetEqual )
   If MinValue = 999999 Then Return False
   
   If IsInBrackets( SourceString$, MinValue ) Then MinValue = FindOperand( SourceString$, MinValue + 1 )
   
   Return MinValue
End Function

Function Minimum( ValueA, ValueB )
   If ValueA < ValueB Then Return ValueA Else Return ValueB
End Function

Comments

Warpy2008
Nice!


Subirenihil2009
Very nice.


_PJ_2009
I'm working on a "Countdown" game (as per the tv show) and this kinda thing is ideal for the 'numbers game' Very nice, thank you, Noobody.


Code Archives Forum