HiveBrain v1.2.0
Get Started
← Back to all entries
patternMinor

Generating and calling code on the fly

Submitted by: @import:stackexchange-codereview··
0
Viewed 0 times
thecodegeneratingandcallingfly

Problem

Delegate

This class module defines what I'm calling, in this context, a Delegate - here a function that can take a number of parameters, evaluate a result, and return a value. Close enough to the actual "delegate" thing I find.

Example usage

Set x = Delegate.Create("(x) => MsgBox(""Hello, "" & x & ""!"")")
x.Execute "Mug"


The Execute call will generate this code in a dedicated code module found in the Reflection project (I know, it should be indented... but hey it's generated code!):

Public Function AnonymousFunction(ByVal x As Variant) As Variant
AnonymousFunction = MsgBox("Hello, " & x & "!")
End Function


Then it will call it (here with parameter value "Mug"), resulting in this:

And this would output VbMsgBoxResult.vbOK, which has a value of 1:

Debug.Print x.Execute("Mug")


Now that's all nice, but I didn't write this class to display "Hello" message boxes; with it I can create a Delegate instance, and pass it as a parameter to a function, say, this member of some Enumerable class:

Public Function Where(predicate As Delegate) As Enumerable

    Dim result As New Collection

    Dim element As Variant
    For Each element In this.Encapsulated
        If predicate.Execute(element) Then result.Add element
    Next

    Set Where = Enumerable.FromCollection(result)

End Function


I've always wanted to be able to do this. Enough talk, here's the code that enables this sorcery!

```
Option Explicit

Private Type TDelegate
Body As String
Parameters As New Collection
End Type

Private Const methodName As String = "AnonymousFunction"
Private this As TDelegate

Friend Property Get Body() As String
Body = this.Body
End Property

Friend Property Let Body(ByVal value As String)
this.Body = value
End Property

Public Function Create(ByVal expression As String) As Delegate

Dim result As New Delegate

Dim regex As New RegExp
regex.Pattern = "\((.)\)\s\=\>\s(.)"

Dim regexMatches

Solution

NOTE - if you decide to stick with paramArray() it wouldn't be a bad idea to check the boundaries of the paramArray() before going any further -> into Select case in the Execute(). Application.Run() is capable to take up to 30 parameters so a quick check that your Ubound(params)) super tiny ;)

but why take a
paramArray() in the Execute() since currently Execute() can only proceed with 10 arguments? (could do with up to 30 due to Application.Run() limit of 30 optional arguments)

Application.Run can take 30 Optional Parameters so I am just thinking that possibly a better idea would be to take up to 10 (or 30) optional parameters rather than a whole paramArray().

The function's definition may not look too pretty with all those Optional Parameters but it would allow you for a (IMO) better function's body.

I suspect that you wouldn't have to drastically change anything in the way you call
Execute() but I haven't tested so this may still need verification.

So...something along these lines:

'//
'// Application.Run() is limited to up to 30 optional arguments
'//
'// firstParameter may actually not needed to be passed because it's a global constant
'// I have used it here "just in case" for now
'//
Public Function Execute(methodName As String, _
                        Optional Arg1 As Variant, Optional Arg2 As Variant, Optional Arg3 As Variant, _
                        Optional Arg4 As Variant, Optional Arg5 As Variant, Optional Arg6 As Variant, _
                        Optional Arg7 As Variant, Optional Arg8 As Variant, Optional Arg9 As Variant, _
                        Optional Arg10 As Variant, Optional Arg11 As Variant, Optional Arg12 As Variant, _
                        Optional Arg13 As Variant, Optional Arg14 As Variant, Optional Arg15 As Variant, _
                        Optional Arg16 As Variant, Optional Arg17 As Variant, Optional Arg18 As Variant, _
                        Optional Arg19 As Variant, Optional Arg20 As Variant, Optional Arg21 As Variant, _
                        Optional Arg22 As Variant, Optional Arg23 As Variant, Optional Arg24 As Variant, _
                        Optional Arg25 As Variant, Optional Arg24 As Variant, Optional Arg27 As Variant, _
                        Optional Arg28 As Variant, Optional Arg29 As Variant, Optional Arg30 As Variant _
                        ) As Variant

    On Error GoTo CleanFail

    GenerateAnonymousMethod
    'cannot break beyond this point

    Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
                              Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
                              Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)

CleanExit:
    DestroyAnonymousMethod
    Exit Function

CleanFail:
    Resume CleanExit
End Function


Ok, so you will need to modify the
AddParameter() too...because Variant can be Missing

Friend Sub AddParameter(ByVal paramName As String)
    this.Parameters.Add "Optional ByVal " & paramName & " As Variant = vbNullString"
End Sub


This reduces all the
Select Case 1-30` to a single:

Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
                              Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
                              Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)


A super easy repro to get an idea just in case the above is a bit overwhelming

Sub Main()
    ExecuteExt
    ExecuteExt "hello"
    ExecuteExt "hello", "world"
End Sub

' your execute without the select
Function ExecuteExt(Optional ByVal Arg1 As Variant, Optional ByVal Arg2 As Variant)
    ExecuteExt = Application.Run("PrintArgs", Arg1, Arg2)
End Function

' this would be the generated anonymous method
Sub PrintArgs(Optional ByVal Arg1 As Variant = vbNullString, Optional ByVal Arg2 As Variant = vbNullString)
    Debug.Print Arg1, Arg2
End Sub

Code Snippets

'//
'// Application.Run() is limited to up to 30 optional arguments
'//
'// firstParameter may actually not needed to be passed because it's a global constant
'// I have used it here "just in case" for now
'//
Public Function Execute(methodName As String, _
                        Optional Arg1 As Variant, Optional Arg2 As Variant, Optional Arg3 As Variant, _
                        Optional Arg4 As Variant, Optional Arg5 As Variant, Optional Arg6 As Variant, _
                        Optional Arg7 As Variant, Optional Arg8 As Variant, Optional Arg9 As Variant, _
                        Optional Arg10 As Variant, Optional Arg11 As Variant, Optional Arg12 As Variant, _
                        Optional Arg13 As Variant, Optional Arg14 As Variant, Optional Arg15 As Variant, _
                        Optional Arg16 As Variant, Optional Arg17 As Variant, Optional Arg18 As Variant, _
                        Optional Arg19 As Variant, Optional Arg20 As Variant, Optional Arg21 As Variant, _
                        Optional Arg22 As Variant, Optional Arg23 As Variant, Optional Arg24 As Variant, _
                        Optional Arg25 As Variant, Optional Arg24 As Variant, Optional Arg27 As Variant, _
                        Optional Arg28 As Variant, Optional Arg29 As Variant, Optional Arg30 As Variant _
                        ) As Variant

    On Error GoTo CleanFail

    GenerateAnonymousMethod
    'cannot break beyond this point

    Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
                              Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
                              Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)

CleanExit:
    DestroyAnonymousMethod
    Exit Function

CleanFail:
    Resume CleanExit
End Function
Friend Sub AddParameter(ByVal paramName As String)
    this.Parameters.Add "Optional ByVal " & paramName & " As Variant = vbNullString"
End Sub
Execute = Application.Run(methodName, Arg1, Arg2, Arg3, Arg4, Arg5, Arg6, Arg7, Arg8, Arg9, _
                              Arg10, Arg11, Arg12, Arg13, Arg14, Arg15, Arg16, Arg17, Arg18, Arg19, _
                              Arg20, Arg21, Arg22, Arg23, Arg24, Arg25, arg26, Arg27, Arg28, Arg29, Arg30)
Sub Main()
    ExecuteExt
    ExecuteExt "hello"
    ExecuteExt "hello", "world"
End Sub

' your execute without the select
Function ExecuteExt(Optional ByVal Arg1 As Variant, Optional ByVal Arg2 As Variant)
    ExecuteExt = Application.Run("PrintArgs", Arg1, Arg2)
End Function

' this would be the generated anonymous method
Sub PrintArgs(Optional ByVal Arg1 As Variant = vbNullString, Optional ByVal Arg2 As Variant = vbNullString)
    Debug.Print Arg1, Arg2
End Sub

Context

StackExchange Code Review Q#66593, answer score: 7

Revisions (0)

No revisions yet.