' 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
Imports System.Reflection.Emit
'''
''' Represents a function call
'''
'''
Friend Class FunctionCallElement
Inherits MemberElement
Private MyArguments As ArgumentList
Private MyMethods As ICollection(Of MethodInfo)
Private MyTargetMethodInfo As CustomMethodInfo
Private MyOnDemandFunctionReturnType As Type
Public Sub New(ByVal name As String, ByVal arguments As ArgumentList)
Me.MyName = name
MyArguments = arguments
End Sub
Friend Sub New(ByVal name As String, ByVal methods As ICollection(Of MethodInfo), ByVal arguments As ArgumentList)
MyName = name
MyArguments = arguments
MyMethods = methods
End Sub
Protected Overrides Sub ResolveInternal()
' Get the types of our arguments
Dim argTypes As Type() = MyArguments.GetArgumentTypes()
' Find all methods with our name on the type
Dim methods As ICollection(Of MethodInfo) = MyMethods
If methods Is Nothing Then
' Convert member info to method info
Dim arr As MemberInfo() = Me.GetMembers(MemberTypes.Method)
Dim arr2(arr.Length - 1) As MethodInfo
Array.Copy(arr, arr2, arr.Length)
methods = arr2
End If
If methods.Count > 0 Then
' More than one method exists with this name
Me.BindToMethod(methods, MyPrevious, argTypes)
Return
End If
' No methods with this name exist; try to bind to an on-demand function
MyOnDemandFunctionReturnType = MyContext.Variables.ResolveOnDemandFunction(MyName, argTypes)
If MyOnDemandFunctionReturnType Is Nothing Then
' Failed to bind to a function
Me.ThrowFunctionNotFoundException(MyPrevious)
End If
End Sub
Private Sub ThrowFunctionNotFoundException(ByVal previous As MemberElement)
If previous Is Nothing Then
MyBase.ThrowCompileException(CompileErrorResourceKeys.UndefinedFunction, CompileExceptionReason.UndefinedName, MyName, MyArguments)
Else
MyBase.ThrowCompileException(CompileErrorResourceKeys.UndefinedFunctionOnType, CompileExceptionReason.UndefinedName, MyName, MyArguments, previous.TargetType.Name)
End If
End Sub
Private Sub ThrowNoAccessibleMethodsException(ByVal previous As MemberElement)
If previous Is Nothing Then
MyBase.ThrowCompileException(CompileErrorResourceKeys.NoAccessibleMatches, CompileExceptionReason.AccessDenied, MyName, MyArguments)
Else
MyBase.ThrowCompileException(CompileErrorResourceKeys.NoAccessibleMatchesOnType, CompileExceptionReason.AccessDenied, MyName, MyArguments, previous.TargetType.Name)
End If
End Sub
Private Sub ThrowAmbiguousMethodCallException()
MyBase.ThrowCompileException(CompileErrorResourceKeys.AmbiguousCallOfFunction, CompileExceptionReason.AmbiguousMatch, MyName, MyArguments)
End Sub
' Try to find a match from a set of methods
Private Sub BindToMethod(ByVal methods As ICollection(Of MethodInfo), ByVal previous As MemberElement, ByVal argTypes As Type())
Dim customInfos As New List(Of CustomMethodInfo)()
' Wrap the MethodInfos in our custom class
For Each mi As MethodInfo In methods
Dim cmi As New CustomMethodInfo(mi)
customInfos.Add(cmi)
Next
' Discard any methods that cannot qualify as overloads
Dim arr As CustomMethodInfo() = customInfos.ToArray()
customInfos.Clear()
For Each cmi As CustomMethodInfo In arr
If cmi.IsMatch(argTypes) = True Then
customInfos.Add(cmi)
End If
Next
If customInfos.Count = 0 Then
' We have no methods that can qualify as overloads; throw exception
Me.ThrowFunctionNotFoundException(previous)
Else
' At least one method matches our criteria; do our custom overload resolution
Me.ResolveOverloads(customInfos.ToArray(), previous, argTypes)
End If
End Sub
' Find the best match from a set of overloaded methods
Private Sub ResolveOverloads(ByVal infos As CustomMethodInfo(), ByVal previous As MemberElement, ByVal argTypes As Type())
' Compute a score for each candidate
For Each cmi As CustomMethodInfo In infos
cmi.ComputeScore(argTypes)
Next
' Sort array from best to worst matches
Array.Sort(Of CustomMethodInfo)(infos)
' Discard any matches that aren't accessible
infos = Me.GetAccessibleInfos(infos)
' No accessible methods left
If infos.Length = 0 Then
Me.ThrowNoAccessibleMethodsException(previous)
End If
' Handle case where we have more than one match with the same score
Me.DetectAmbiguousMatches(infos)
' If we get here, then there is only one best match
MyTargetMethodInfo = infos(0)
End Sub
Private Function GetAccessibleInfos(ByVal infos As CustomMethodInfo()) As CustomMethodInfo()
Dim accessible As New List(Of CustomMethodInfo)()
For Each cmi As CustomMethodInfo In infos
If cmi.IsAccessible(Me) = True Then
accessible.Add(cmi)
End If
Next
Return accessible.ToArray()
End Function
' Handle case where we have overloads with the same score
Private Sub DetectAmbiguousMatches(ByVal infos As CustomMethodInfo())
Dim sameScores As New List(Of CustomMethodInfo)()
Dim first As CustomMethodInfo = infos(0)
' Find all matches with the same score as the best match
For Each cmi As CustomMethodInfo In infos
If DirectCast(cmi, IEquatable(Of CustomMethodInfo)).Equals(first) = True Then
sameScores.Add(cmi)
End If
Next
' More than one accessible match with the same score exists
If sameScores.Count > 1 Then
Me.ThrowAmbiguousMethodCallException()
End If
End Sub
Protected Overrides Sub Validate()
MyBase.Validate()
If Not MyOnDemandFunctionReturnType Is Nothing Then
Return
End If
' Any function reference in an expression must return a value
If Me.Method.ReturnType Is GetType(Void) Then
MyBase.ThrowCompileException(CompileErrorResourceKeys.FunctionHasNoReturnValue, CompileExceptionReason.FunctionHasNoReturnValue, MyName)
End If
End Sub
Public Overrides Sub Emit(ByVal ilg As FleeILGenerator, ByVal services As IServiceProvider)
MyBase.Emit(ilg, services)
Dim elements As ExpressionElement() = MyArguments.ToArray()
' If we are an on-demand function, then emit that and exit
If Not MyOnDemandFunctionReturnType Is Nothing Then
Me.EmitOnDemandFunction(elements, ilg, services)
Return
End If
Dim isOwnerMember As Boolean = MyOptions.IsOwnerType(Me.Method.ReflectedType)
' Load the owner if required
If MyPrevious Is Nothing AndAlso isOwnerMember = True AndAlso Me.IsStatic = False Then
Me.EmitLoadOwner(ilg)
End If
Me.EmitFunctionCall(Me.NextRequiresAddress, ilg, services)
End Sub
Private Sub EmitOnDemandFunction(ByVal elements As ExpressionElement(), ByVal ilg As FleeILGenerator, ByVal services As IServiceProvider)
' Load the variable collection
EmitLoadVariables(ilg)
' Load the function name
ilg.Emit(OpCodes.Ldstr, MyName)
' Load the arguments array
EmitElementArrayLoad(elements, GetType(Object), ilg, services)
' Call the function to get the result
Dim mi As MethodInfo = VariableCollection.GetFunctionInvokeMethod(MyOnDemandFunctionReturnType)
Me.EmitMethodCall(mi, ilg)
End Sub
' Emit the arguments to a paramArray method call
Private Sub EmitParamArrayArguments(ByVal parameters As ParameterInfo(), ByVal elements As ExpressionElement(), ByVal ilg As FleeILGenerator, ByVal services As IServiceProvider)
' Get the fixed parameters
Dim fixedParameters(MyTargetMethodInfo.MyFixedArgTypes.Length - 1) As ParameterInfo
Array.Copy(parameters, fixedParameters, fixedParameters.Length)
' Get the corresponding fixed parameters
Dim fixedElements(MyTargetMethodInfo.MyFixedArgTypes.Length - 1) As ExpressionElement
Array.Copy(elements, fixedElements, fixedElements.Length)
' Emit the fixed arguments
Me.EmitRegularFunctionInternal(fixedParameters, fixedElements, ilg, services)
' Get the paramArray arguments
Dim paramArrayElements(elements.Length - fixedElements.Length - 1) As ExpressionElement
Array.Copy(elements, fixedElements.Length, paramArrayElements, 0, paramArrayElements.Length)
' Emit them into an array
EmitElementArrayLoad(paramArrayElements, MyTargetMethodInfo.ParamArrayElementType, ilg, services)
End Sub
' Emit elements into an array
Private Shared Sub EmitElementArrayLoad(ByVal elements As ExpressionElement(), ByVal arrayElementType As Type, ByVal ilg As FleeILGenerator, ByVal services As IServiceProvider)
' Load the array length
LiteralElement.EmitLoad(elements.Length, ilg)
' Create the array
ilg.Emit(OpCodes.Newarr, arrayElementType)
' Store the new array in a unique local and remember the index
Dim local As LocalBuilder = ilg.DeclareLocal(arrayElementType.MakeArrayType())
Dim arrayLocalIndex As Integer = local.LocalIndex
Utility.EmitStoreLocal(ilg, arrayLocalIndex)
For i As Integer = 0 To elements.Length - 1
' Load the array
Utility.EmitLoadLocal(ilg, arrayLocalIndex)
' Load the index
LiteralElement.EmitLoad(i, ilg)
' Emit the element (with any required conversions)
Dim element As ExpressionElement = elements(i)
element.Emit(ilg, services)
ImplicitConverter.EmitImplicitConvert(element.ResultType, arrayElementType, ilg)
' Store it into the array
Utility.EmitArrayStore(ilg, arrayElementType)
Next
' Load the array
Utility.EmitLoadLocal(ilg, arrayLocalIndex)
End Sub
Public Sub EmitFunctionCall(ByVal nextRequiresAddress As Boolean, ByVal ilg As FleeILGenerator, ByVal services As IServiceProvider)
Dim parameters As ParameterInfo() = Me.Method.GetParameters()
Dim elements As ExpressionElement() = MyArguments.ToArray()
' Emit either a regular or paramArray call
If MyTargetMethodInfo.IsParamArray = False Then
Me.EmitRegularFunctionInternal(parameters, elements, ilg, services)
Else
Me.EmitParamArrayArguments(parameters, elements, ilg, services)
End If
MemberElement.EmitMethodCall(Me.ResultType, nextRequiresAddress, Me.Method, ilg)
End Sub
' Emit the arguments to a regular method call
Private Sub EmitRegularFunctionInternal(ByVal parameters As ParameterInfo(), ByVal elements As ExpressionElement(), ByVal ilg As FleeILGenerator, ByVal services As IServiceProvider)
Debug.Assert(parameters.Length = elements.Length, "argument count mismatch")
' Emit each element and any required conversions to the actual parameter type
For i As Integer = 0 To parameters.Length - 1
Dim element As ExpressionElement = elements(i)
Dim pi As ParameterInfo = parameters(i)
element.Emit(ilg, services)
Dim success As Boolean = ImplicitConverter.EmitImplicitConvert(element.ResultType, pi.ParameterType, ilg)
Debug.Assert(success, "conversion failed")
Next
End Sub
'''
''' The method info we will be calling
'''
Private ReadOnly Property Method() As MethodInfo
Get
Return MyTargetMethodInfo.Target
End Get
End Property
Public Overrides ReadOnly Property ResultType() As Type
Get
If Not MyOnDemandFunctionReturnType Is Nothing Then
Return MyOnDemandFunctionReturnType
Else
Return Me.Method.ReturnType
End If
End Get
End Property
Protected Overrides ReadOnly Property RequiresAddress() As Boolean
Get
Return Not IsGetTypeMethod(Me.Method)
End Get
End Property
Protected Overrides ReadOnly Property IsPublic() As Boolean
Get
Return Me.Method.IsPublic
End Get
End Property
Public Overrides ReadOnly Property IsStatic() As Boolean
Get
Return Me.Method.IsStatic
End Get
End Property
End Class