' 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