Code archives/Algorithms/Expression Evaluator

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

Download source code

Expression Evaluator by grable2007
This little pieace of code parses & evaluates string expressions.

It handles variables,constants and functions with 0 to 2 parameters, like Sin/Cos/Atan2 etc.

Allso able to assign the value to registered variables or temporary variables.

UPDATE: now handles several lines seperated by ;

Public API:
Function RegisterVariable( ident:String, p:Double Ptr)
Function RegisterConstant( ident:String, value:Double)
Function RegisterFunction( ident:String, p:Byte Ptr, pcount:Int)
Function UnregisterIdent( ident:String)
Function UnregisterAllIdents()
Function ClearIdentValues()
Function Expression:Double( s:String)

Example usage:
Global a:Double = 5
RegisterConstant( "pi", Pi)
RegisterVariable( "a", Varptr a)
RegisterFunction( "sin", Sin, 1)
RegisterFunction( "atan2", ATan2, 2)

Print "> sin(pi * a) + atan2(-2 / 1, a - 2)"
Print "="+(Sin(Pi * a) + ATan2(-2 / 1, a - 2))
Print "----------------------------------------"
Print "=" + Expression( "x = sin(pi * a) + atan2(-2 / 1, a - 2)")
Print "=" + Expression( "a = x;~n a * 2")
ClearIdentValues()
Print "=" + Expression( "x")
Print "=" + a
Import BRL.StandardIO
Import BRL.Map
Import BRL.Math

Private

Const EXPR_VAR:Int = 1
Const EXPR_VALUE:Int = 2
Const EXPR_CONST:Int = 3
Const EXPR_FUNC0:Int = 4
Const EXPR_FUNC1:Int = 5
Const EXPR_FUNC2:Int = 6

Type TExprIdent
	Field Tag:Int
	Field Value:Double
	Field V:Double Ptr	
	Field F0:Double()
	Field F1:Double( value:Double)
	Field F2:Double( value1:Double, value2:Double)

	Function CreateVar:TExprIdent( p:Double Ptr)
		Local v:TExprIdent = New TExprIdent
		v.Tag = EXPR_VAR
		v.V = p
		Return v
	EndFunction
	
	Function CreateConst:TExprIdent( value:Double)
		Local v:TExprIdent = New TExprIdent
		v.Tag = EXPR_CONST
		v.Value = value
		Return v		
	EndFunction
	
	Function CreateValue:TExprIdent( value:Double)
		Local v:TExprIdent = New TExprIdent
		v.Tag = EXPR_VALUE
		v.Value = value
		Return v		
	EndFunction	

	Function CreateFunc:TExprIdent( pcount:Int, p:Byte Ptr)
		Local v:TExprIdent = New TExprIdent
		Select pcount
			Case 0 
				v.Tag = EXPR_FUNC0
				v.F0 = p
			Case 1 
				v.Tag = EXPR_FUNC1
				v.F1 = p
			Case 2 
				v.Tag = EXPR_FUNC2				
				v.F2 = p
		EndSelect		
		Return v
	EndFunction	
EndType

Global source:String
Global pos:Int
Global idents:TMap = New TMap

Function ReportError( s:String, printpos:Int = True)
	If printpos Then 
		Print "ERROR: pos="+pos+" : "+s
	Else
		Print "ERROR: "+s
	EndIf
EndFunction

Function LookupVariable:Double( ident:String)
	Local v:TExprIdent = TExprIdent( idents.ValueForKey( ident))
	If v Then 
		Select v.Tag
			Case EXPR_VAR 
				Return v.V[0]
			Case EXPR_VALUE, EXPR_CONST 
				Return v.Value
			Default
				ReportError( "identifier not a variable => " + ident, False)
		EndSelect		
		Return 0
	EndIf
	ReportError( "variable not defined => " + ident, False)
	Return 0
EndFunction

Function CallFunction:Double( ident:String, pcount:Int=0,  value1:Double=0, value2:Double=0)
	Local v:TExprIdent = TExprIdent( idents.ValueForKey( ident))
	If v Then
		Select v.Tag
			Case EXPR_FUNC0 
				If pcount = 0 Then Return v.F0()
				ReportError( "invalid parameter count " + pcount + " expected 0")
			Case EXPR_FUNC1 
				If pcount = 1 Then Return v.F1( value1)
				ReportError( "invalid parameter count " + pcount + " expected 1")
			Case EXPR_FUNC2 
				If pcount = 2 Then Return v.F2( value1, value2)
				ReportError( "invalid parameter count " + pcount + " expected 2")
			Default
				ReportError( "identifier not a function => " + ident, False)				
		EndSelect
		Return 0
	EndIf
	ReportError( "function not defined => " + ident, False)
	Return 0
EndFunction

Function EatWhitespace()
	While (source[pos] = Asc(" ")) Or (source[pos] = Asc("~t")) Or (source[pos] = Asc("~n")) Or (source[pos] = Asc("~r"))
		pos :+ 1
	Wend
EndFunction

Function EatIdent:String()
	Local start:Int = pos
	While ((source[pos] >= Asc("a")) And (source[pos] <= Asc("z"))) Or ..
		((source[pos] >= Asc("A")) And (source[pos] <= Asc("Z"))) Or ..
		((source[pos] >= Asc("0")) And (source[pos] <= Asc("9"))) Or (source[pos] = Asc("_"))
		pos :+ 1
	Wend
	Return source[start..pos]
EndFunction

Function EatNumber:Double()
	Local start:Int = pos
	Local gotsep:Int = False
	Local res:String	
	While (source[pos] >= Asc("0")) And (source[pos] <= Asc("9"))
		pos :+ 1
		If source[pos] = Asc(".") Then
			If gotsep Then ReportError( "error in Double number")
			gotsep = True
			pos :+ 1
		EndIf
	Wend
	Return source[start..pos].ToDouble()
EndFunction

Function Primary:Double()
	Local lvalue:Double
	EatWhitespace()
	If source[pos] = Asc("(") Then
		pos :+ 1
		lvalue = AddExpression()
    		If source[pos] <> Asc(")") Then ReportError( "expected )")
    		pos :+ 1
	ElseIf (source[pos] >= Asc("0")) And (source[pos] <= Asc("9")) Then
		lvalue = EatNumber()
	ElseIf source[pos] = Asc("-") Then
		pos :+ 1
		lvalue = - AddExpression()
	ElseIf ((source[pos] >= Asc("a")) And (source[pos] <= Asc("z"))) Or ..
		((source[pos] >= Asc("A")) And (source[pos] <= Asc("Z"))) Or (source[pos] = Asc("_")) Then
		Local ident:String = EatIdent()
		If source[pos] = Asc("(") Then
			pos :+ 1
			EatWhitespace()
			If source[pos] = Asc(")") Then
				' no parameters
				pos :+ 1
				lvalue = CallFunction( ident)
			Else			
				Local rvalue1:Double = AddExpression()
				If source[pos] = Asc(")") Then
					' 1 parameter
					pos :+ 1
					lvalue = CallFunction( ident, 1, rvalue1)
				ElseIf source[pos] = Asc(",") Then
					' 2 parameters
					pos :+ 1
					Local rvalue2:Double = AddExpression()					
		    			If source[pos] <> Asc(")") Then ReportError( "expected )")
	    				pos :+ 1			
					lvalue = CallFunction( ident, 2, rvalue1, rvalue2)
				Else
					ReportError( "invalid function expression => " + ident)
				EndIf
			EndIf
		Else
			' variable lookup
			lvalue = LookupVariable( ident)
		EndIf
	Else
		ReportError( "expected number or -number or (expression)")
	EndIf
	EatWhitespace()
	Return lvalue
EndFunction

Function MulExpression:Double()
	Local lvalue:Double, rvalue:Double
	EatWhitespace()
	lvalue = Primary()
	While (source[pos] = Asc("*")) Or (source[pos] = Asc("/"))
		If source[pos] = Asc("*") Then
			pos :+ 1
			rvalue = Primary()
			lvalue = lvalue * rvalue
		ElseIf source[pos] = Asc("/") Then
			pos :+ 1
			rvalue = Primary()
			lvalue = lvalue / rvalue
		EndIf
	Wend
	EatWhitespace()
	Return lvalue
EndFunction
	
Function AddExpression:Double()
	Local lvalue:Double, rvalue:Double
	EatWhitespace()
	lvalue = MulExpression()
	While (source[pos] = Asc("+")) Or (source[pos] = Asc("-")) 
		If source[pos] = Asc("+") Then
			pos :+ 1
			rvalue = MulExpression()
			lvalue = lvalue + rvalue
		ElseIf source[pos] = Asc("-") Then
			pos :+ 1
			rvalue = MulExpression()
			lvalue = lvalue - rvalue		
		EndIf
	Wend
	EatWhitespace()
	Return lvalue
EndFunction

Public

Function RegisterVariable( ident:String, p:Double Ptr)
	idents.Insert( ident, TExprIdent.CreateVar( p))	
EndFunction

Function RegisterConstant( ident:String, value:Double)
	idents.Insert( ident, TExprIdent.CreateConst( value))	
EndFunction

Function RegisterFunction( ident:String, p:Byte Ptr, pcount:Int)
	If (pcount >= 0) And (pcount <= 2) Then idents.Insert( ident, TExprIdent.CreateFunc( pcount, p))
EndFunction

Function UnregisterIdent( ident:String)
	idents.Remove( ident)
EndFunction

Function UnregisterAllIdents()
	idents.Clear()
EndFunction

Function ClearIdentValues()
	For Local node:TNode = EachIn idents
		If TExprIdent(node._value).Tag = EXPR_VALUE Then idents.Remove( node._key)
	Next
EndFunction

Function Expression:Double( s:String)
	Local result:Double, ident:String, idx:Int
	source = s.Trim()
	pos = 0
	idx = source.Find( "=")
	If idx > 0 Then
		ident = EatIdent()
		EatWhitespace()
		pos :+ 1
	ElseIf idx = 0 Then
		ReportError( "invalid assignment", False)
		Return 0
	EndIf
	result = AddExpression()
	If ident.Length > 0 Then 
		Local v:TExprIdent = TExprIdent( idents.ValueForKey( ident))
		If v Then 
			Select v.Tag
				Case EXPR_VAR 
					v.V[0] = result
				Case EXPR_VALUE
					v.Value = result
				Case EXPR_CONST 
					ReportError( "unable to assign to constant " + ident, False)
			EndSelect
		Else
			idents.Insert( ident, TExprIdent.CreateValue( result))
		EndIf		
	EndIf
	' more "lines" ?
	If pos < source.Length Then
		If source[pos] = Asc(";") Then
			pos :+ 1
			If pos < source.Length Then
				EatWhitespace()
				Return Expression( source[pos..])
			EndIf
			Return result
		EndIf
		ReportError( "invalid expression")
		Return 0		
	EndIf
	Return result
EndFunction

Comments

None.

Code Archives Forum