Click here to Skip to main content
15,885,546 members
Articles / Programming Languages / Visual Basic

Flee - Fast Lightweight Expression Evaluator

Rate me:
Please Sign up or sign in to vote.
4.91/5 (47 votes)
11 Oct 2007LGPL310 min read 195.4K   3.7K   108  
A .NET expression evaluator that compiles to IL and is designed for speed.
' This library is free software; you can redistribute it and/or
' modify it under the terms of the GNU Lesser General Public License
' as published by the Free Software Foundation; either version 2.1
' of the License, or (at your option) any later version.
' 
' This library is distributed in the hope that it will be useful,
' but WITHOUT ANY WARRANTY; without even the implied warranty of
' MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
' Lesser General Public License for more details.
' 
' You should have received a copy of the GNU Lesser General Public
' License along with this library; if not, write to the Free
' Software Foundation, Inc., 59 Temple Place, Suite 330, Boston,
' MA 02111-1307, USA.
' 
' Flee - Fast Lightweight Expression Evaluator
' Copyright � 2007 Eugene Ciloci
'

Imports System.Reflection.Emit
Imports System.Reflection
Imports System.ComponentModel.Design

''' <include file='DocComments.xml' path='DocComments/Member[@name="Expression"]/*' />
<Serializable()> _
Public NotInheritable Class Expression
	Implements System.Runtime.Serialization.ISerializable
	Implements System.Runtime.Serialization.IDeserializationCallback

	' The expression parser shared between all expressions
	Private Shared OurParser As ExpressionParser = CreateParser()
	' Our expression pattern
	Private MyExpression As String
	' The delegate that points our dynamic method
	Private MyDynamicMethodDelegate As System.Delegate
	' The owner class of our expression
	Private MyOwner As Object
	' Our options
	Private MyOptions As ExpressionOptions
	' Serialized version
	Private Const VERSION As Integer = 1

	Private Shared SyncRoot As New Object

	''' <include file='DocComments.xml' path='DocComments/Member[@name="Expression.New2"]/*' />	
	Public Sub New(ByVal expression As String, ByVal owner As Object, ByVal options As ExpressionOptions)
		Me.ValidateCreateParams(expression, owner, options)
		MyExpression = expression
		MyOwner = owner

		' Add the owner type to the imports
		options.Imports.SetOwnerImport(owner.GetType())
		' Make sure we clone the options
		MyOptions = options.Clone()

		MyDynamicMethodDelegate = Me.Compile(expression, owner, MyOptions)
	End Sub

	''' <include file='DocComments.xml' path='DocComments/Member[@name="Expression.New1"]/*' />
	Public Sub New(ByVal expression As String, ByVal owner As Object)
		Me.New(expression, owner, New ExpressionOptions())
	End Sub

	Private Sub New(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext)
		MyExpression = info.GetString("Expression")
		MyOwner = info.GetValue("Owner", GetType(Object))
		MyOptions = info.GetValue("Options", GetType(ExpressionOptions))
	End Sub

	Private Sub GetObjectData(ByVal info As System.Runtime.Serialization.SerializationInfo, ByVal context As System.Runtime.Serialization.StreamingContext) Implements System.Runtime.Serialization.ISerializable.GetObjectData
		info.AddValue("Version", VERSION)
		info.AddValue("Expression", MyExpression)
		info.AddValue("Owner", MyOwner)
		info.AddValue("Options", MyOptions)
	End Sub

	Private Sub OnDeserialization(ByVal sender As Object) Implements System.Runtime.Serialization.IDeserializationCallback.OnDeserialization
		MyDynamicMethodDelegate = Me.Compile(MyExpression, MyOwner, MyOptions)
	End Sub

	Private Function Compile(ByVal expression As String, ByVal owner As Object, ByVal options As ExpressionOptions) As System.Delegate
		Dim ownerType As Type = owner.GetType()

		' Add the services that will be used by elements during the compile
		Dim services As IServiceContainer = New ServiceContainer()
		Me.AddServices(services)

		' Parse and get the root element of the parse tree
		Dim topElement As ExpressionElement = Parse(expression, services)

		If options.ResultType Is Nothing Then
			options.ResultType = topElement.ResultType
		End If

		Dim rootElement As New RootExpressionElement(topElement, options.ResultType)

		' Create the dynamic method
		Dim dm As New DynamicMethod(String.Empty, options.ResultType, New Type() {ownerType}, ownerType)
		Dim ilg As ILGenerator = dm.GetILGenerator()

		' Emit the IL
		rootElement.Emit(ilg, services)

		' Create the delegate
		Dim delegateType As Type = GetType(ExpressionEvaluator(Of )).MakeGenericType(options.ResultType)
		Return dm.CreateDelegate(delegateType, owner)
	End Function

	Private Sub ValidateCreateParams(ByVal expression As String, ByVal owner As Object, ByVal options As ExpressionOptions)
		AssertNotNull(expression, "expression")
		AssertNotNull(owner, "owner")
		AssertNotNull(options, "options")

		If owner.GetType().IsValueType = True Then
			' Only allow reference types as owners for now
			Throw New ArgumentException("Owner must be a reference type")
		End If
	End Sub

	Private Sub AddServices(ByVal dest As IServiceContainer)
		dest.AddService(GetType(ExpressionOptions), MyOptions)
		dest.AddService(GetType(Expression), Me)
		dest.AddService(GetType(TempLocalManager), New TempLocalManager())

		Dim deo As IDynamicExpressionOwner = TryCast(MyOwner, IDynamicExpressionOwner)

		If (Not deo Is Nothing) AndAlso (Not deo.Engine Is Nothing) Then
			dest.AddService(GetType(CalculationEngine), deo.Engine)
		End If
	End Sub

	' Does the actual parsing of an expression.  Thead-safe.
	Private Shared Function Parse(ByVal expression As String, ByVal services As IServiceContainer) As ExpressionElement
		SyncLock SyncRoot
			Dim sr As New System.IO.StringReader(expression)
			OurParser.Reset()
			OurParser.Tokenizer.Reset(sr)
			Dim analyzer As CustomExpressionAnalyzer = OurParser.Analyzer

			analyzer.SetServices(services)

			Dim rootNode As PerCederberg.Grammatica.Runtime.Node = DoParse()
			analyzer.Reset()
			Dim topElement As ExpressionElement = rootNode.Values.Item(0)
			Return topElement
		End SyncLock
	End Function

	Private Shared Function DoParse() As PerCederberg.Grammatica.Runtime.Node
		Try
			Return OurParser.Parse()
		Catch ex As PerCederberg.Grammatica.Runtime.ParserLogException
			' Syntax error; wrap it in our exception and rethrow
			Throw New ExpressionCompileException(ex)
		End Try
	End Function

	Private Shared Function CreateParser() As ExpressionParser
		Dim sr As New System.IO.StringReader(String.Empty)
		Dim analyzer As New CustomExpressionAnalyzer
		Return New ExpressionParser(sr, analyzer)
	End Function

	Friend Shared Sub AssertNotNull(ByVal o As Object, ByVal paramName As String)
		If o Is Nothing Then
			Throw New ArgumentNullException(paramName)
		End If
	End Sub

	''' <include file='DocComments.xml' path='DocComments/Member[@name="Expression.EmitToAssembly"]/*' />	
	Public Sub EmitToAssembly()
		Dim assemblyName As New AssemblyName()
		assemblyName.Name = "ExpressionAssembly"

		Dim assemblyBuilder As AssemblyBuilder = System.AppDomain.CurrentDomain.DefineDynamicAssembly(assemblyName, AssemblyBuilderAccess.Save)
		Dim moduleBuilder As ModuleBuilder = assemblyBuilder.DefineDynamicModule("ExpressionModule", "Expression.dll")

		Dim mb As MethodBuilder = moduleBuilder.DefineGlobalMethod("Evaluate", MethodAttributes.Public Or MethodAttributes.Static, MyOptions.ResultType, New Type() {MyOwner.GetType()})
		Dim ilg As ILGenerator = mb.GetILGenerator()

		Dim services As IServiceContainer = New ServiceContainer()
		Me.AddServices(services)

		Dim topElement As ExpressionElement = Parse(MyExpression, services)

		Dim rootElement As New RootExpressionElement(topElement, MyOptions.ResultType)
		rootElement.Emit(ilg, services)

		moduleBuilder.CreateGlobalFunctions()
		assemblyBuilder.Save("Expression.dll")
	End Sub

	''' <include file='DocComments.xml' path='DocComments/Member[@name="Expression.ToString"]/*' />	
	Public Overrides Function ToString() As String
		Return MyExpression
	End Function

	''' <include file='DocComments.xml' path='DocComments/Member[@name="Expression.Evaluator"]/*' />	
	Public ReadOnly Property Evaluator() As System.Delegate
		Get
			Return MyDynamicMethodDelegate
		End Get
	End Property

	Friend ReadOnly Property OwnerType() As Type
		Get
			Return MyOwner.GetType()
		End Get
	End Property

	Friend ReadOnly Property Owner() As Object
		Get
			Return MyOwner
		End Get
	End Property

	Friend ReadOnly Property ResultType() As Type
		Get
			Return MyOptions.ResultType
		End Get
	End Property
End Class

By viewing downloads associated with this article you agree to the Terms of Service and the article's licence.

If a file you wish to view isn't highlighted, and is a text file (not binary), please let us know and we'll add colourisation support for it.

License

This article, along with any associated source code and files, is licensed under The GNU Lesser General Public License (LGPLv3)


Written By
Web Developer
Canada Canada
This member has not yet provided a Biography. Assume it's interesting and varied, and probably something to do with programming.

Comments and Discussions