OuttaCyTE
asked on
Dynamically modify/instrument code in a module at design time
Hello VB/VBA experts,
Having futzed around with coding some stuff in Access VBA, I find that the debugging/logging functions are inadequate. I have written a few public routines that help figure out what went wrong by capturing my entry & exit events and a few other items. The problem is maintaining the code instrumentation as I add, delete and restructure the program code itself.
What I would like to be able to do, while running the IDE, is to be able to execute a piece of code that can update the modules of the project to maintain the code instrumenation.
Surely there is a way to do this?
Where would I look for examples? I will increase points as appropriate if this question gets into some gory details and/or significant code examples.
Thanks everyone,
-g
Having futzed around with coding some stuff in Access VBA, I find that the debugging/logging functions are inadequate. I have written a few public routines that help figure out what went wrong by capturing my entry & exit events and a few other items. The problem is maintaining the code instrumentation as I add, delete and restructure the program code itself.
What I would like to be able to do, while running the IDE, is to be able to execute a piece of code that can update the modules of the project to maintain the code instrumenation.
Surely there is a way to do this?
Where would I look for examples? I will increase points as appropriate if this question gets into some gory details and/or significant code examples.
Thanks everyone,
-g
ASKER CERTIFIED SOLUTION
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
ASKER
BrianGEFF719,
Thanks for responding. I've taken a look at the link. Does creating an add-in require the full VB, or can it be done in Access VBA?
List,
I've got some more updates to the routine. The above code works for non-form modules only. Also, it adds a line after every sub/function. I've got those resolved. I'll probably close this in a few days if no one is interested.
Thanks for responding. I've taken a look at the link. Does creating an add-in require the full VB, or can it be done in Access VBA?
List,
I've got some more updates to the routine. The above code works for non-form modules only. Also, it adds a line after every sub/function. I've got those resolved. I'll probably close this in a few days if no one is interested.
ASKER
Brian gets the points because he responded. Grade B because no followup
ASKER
I make no warranties to the correctness or usefullness of this code. I would recommend you treat it as buggy code and backup before you try it on code you don't want trashed.
I would appreciate anyone making updates, fixes, improvements to send me a copy. Send to Contact "the shifted 2" outtacyte.com. If you find this useful, post me a note.
I commented out the actual save so that somebody running this without reading won't actually change their code.
-g
********* cut and paste into a module named InstrumentMe ***** Run the InstrumentMe routine from the IDE ******** Good Luck
Option Compare Database
Option Explicit
'
Dim myOldCode As String
Dim myNewCode As String
'
Private Sub InstrumentMe()
Dim myModule As Module
Dim myComponent As Object 'a VBComponent
'Dim nWhereFound As Long
Dim oRegExpOuter As RegExp
Dim oMatchCollOuter As MatchCollection
Dim oMatchOuter As Match
Dim nRemainderOffset
Dim nIndex As Long
Set oRegExpOuter = New RegExp
oRegExpOuter.Global = True
oRegExpOuter.IgnoreCase = True
'form? = type 100, codemodule = type 1,
For Each myComponent In VBE.ActiveVBProject.VBComp
If myComponent.Type = 1 And myComponent.Name <> "InstrumentMe" Then
Do While True ' infinite loop here. Must provide exit. All because you can't "Next" inside an If/End If pair
Debug.Print myComponent.Name
myNewCode = ""
DoCmd.OpenModule myComponent.Name
Set myModule = Modules(myComponent.Name)
myOldCode = myModule.Lines(1, myModule.CountOfLines)
'myOldCode = "prefix stuff" & vbCrLf & "'dbgInstrumentMe:Off" & vbCrLf & _
"Sub SamSpade(pvar1, optional pvar2 as type) as Returnvartype ...." & vbCrLf & _
"somecode" & vbCrLf & "Exit Function" & vbCrLf & _
"End something not function or sub" & vbCrLf & _
"End Sub ..." & vbCrLf & _
"" & vbCrLf & _
"Friend Static Function SamSpade2(pvar1, optional pvar2 as type) as Returnvartype ...." & vbCrLf & _
"somecode" & vbCrLf & "Exit Function" & vbCrLf & _
"End something not function or sub" & vbCrLf & _
"End Function ..." & vbCrLf & _
"Property Get SamSpadexx(pvar1, optional pvar2 as type) as Returnvartype ...." & vbCrLf & _
"somecode" & vbCrLf & "Exit Property" & vbCrLf & _
"End something not function or sub" & vbCrLf & _
"End Property ..." & vbCrLf & _
"Public Property Get SamSpadexx(pvar1, optional pvar2 as type) as Returnvartype ...." & vbCrLf & _
"somecode" & vbCrLf & "Exit Property" & vbCrLf & _
"End something not function or sub" & vbCrLf & _
"End Property ..." & vbCrLf & _
"Function SamSpade3(pvar1, optional pvar2 as type) as Returnvartype ...." & vbCrLf & _
"somecode" & vbCrLf & "Exit Function" & vbCrLf & _
"End something not function or sub" & vbCrLf & _
"End Function ..." & vbCrLf & "and some stuff on the end"
'Debug.Print myOldCode
myOldCode = Replace(myOldCode, vbCrLf, Chr(8)) ' change cr/lf to something else not normally found in code
'Debug.Print myOldCode
'this regex creates a set of matches. Each match has "prefix stuff" and one entire Function/Sub routine (ending with the line containing End Function/End Sub)
'There are six submatches per match.
' 0) The "prefix stuff" before the Function/Sub - ends with chr(8) - note that if this is the second or following match it may have some leading chr(8) characters as well.
' Prefix stuff is where the declarations will be as well as non-sub and non-function (e.g. property routines) will end up.
' 1) Private, Public, Friend, or ""
' 2) Static or ""
' 3) Sub or Function
' 4) The function except for the End Sub/Function line. Has a leading space
' 5) The End Sub/Function line
'
'At the end, after looping through the match sets, there will be a remainder which is not represented in any of the matches.
'Grab this by getting the offset of the last match + length of last match
oRegExpOuter.pattern = "(.*?)" & Chr(8) & "(Private|Public|Friend)?[
Set oMatchCollOuter = oRegExpOuter.Execute(myOld
'Debug.Print "there are " & oMatchCollOuter.Count & " matches in the collection"
'Debug.Print oMatchCollOuter.Item(0).Su
If InStr(oMatchCollOuter.Item
DoCmd.Close acModule, myComponent.Name, acSaveNo
Exit Do ' no instrumenting desired for this module
End If
For Each oMatchOuter In oMatchCollOuter
'Debug.Print "Match: " & oMatchOuter.Value ' the contents of this match
'Debug.Print oMatchOuter.FirstIndex ' the index into the original string for this match
'Debug.Print oMatchOuter.Length ' the length of this match (length of value)
' 'submatches are not a match collection
'Debug.Print oMatchOuter.SubMatches.Cou
'For nIndex = 0 To oMatchOuter.SubMatches.Cou
' Debug.Print "Item(" & nIndex & "): " & oMatchOuter.SubMatches.Ite
'Next nIndex
nRemainderOffset = oMatchOuter.FirstIndex + oMatchOuter.Length + 2
instrumentRoutines oMatchOuter.SubMatches.Ite
Trim(Trim(oMatchOuter.SubM
oMatchOuter.SubMatches.Ite
oMatchOuter.SubMatches.Ite
oMatchOuter.SubMatches.Ite
myComponent.Name, _
Trim(Left(oMatchOuter.SubM
Next 'get the next match
'handle the remainder
'Debug.Print Mid(myOldCode, nRemainderOffset)
myNewCode = myNewCode & Replace(Mid(myOldCode, nRemainderOffset), Chr(8), vbCrLf) ' just copy it as is
'When you are comfortable with the code, you can comment the next two and uncomment the following block
'and this thing will instrument your code.
'I suggest you backup first and I accept no liabilities for this rough piece of code!!
Debug.Print myNewCode
DoCmd.Close acModule, myComponent.Name, acSaveNo
'now, replace all of the lines in the module with the new code !!!!
'myModule.DeleteLines 1, myModule.CountOfLines ' all of them
'myModule.InsertLines 1, myNewCode
'DoCmd.Close acModule, myComponent.Name, acSaveYes
Exit Do 'exit do loop at bottom
Loop
End If
Next myComponent ' go get the next module
End Sub
Private Sub instrumentRoutines(pPrefix
Dim myLines() As String ' holds my old lines
Dim myNewLines() As String 'holds my new lines
Dim myNewLinesSize As Long
Dim nCurrLine As Long ' index into old lines
Dim nNewLine As Long ' index into new lines, next place to insert
Dim bInstrumentRoutine As Boolean
Dim nWhereFound As Long
Dim sExitString As String
Dim sEndString As String
Dim sEndStringSize As Long
'Debug.Print "Prefix: " & pPrefix
'Debug.Print "Code: " & pRoutine
'Debug.Print "Type: " & pType
'Debug.Print "Module: " & pModuleName
'Debug.Print "Routine: " & pRoutineName
myNewCode = myNewCode & Replace(pPrefix, Chr(8), vbCrLf) & vbCrLf ' just copy it as is, supply dropped cr/lf
myLines = Split(pRoutine, Chr(8))
bInstrumentRoutine = True
nNewLine = 0
myNewLinesSize = fArraySize(myLines) + 50
ReDim myNewLines(myNewLinesSize)
sExitString = "Exit " & pType
sEndString = "End " & pType
sEndStringSize = Len(sEndString)
' handle the first line
If InStr(myLines(LBound(myLin
bInstrumentRoutine = False
End If
myNewLines(nNewLine) = myLines(LBound(myLines))
myNewLines(nNewLine + 1) = " dbgEntry (""" & pModuleName & "." & pRoutineName & """)"
nNewLine = nNewLine + 2
For nCurrLine = LBound(myLines) + 1 To UBound(myLines)
'this routine could use some improvement. For now, you cannot comment an Exit Sub/Function or End Sub/Function statements
'The Exit Sub/Function cannot be in an if xxx then yyy else zzz type statement. Must use end if type structure
'the dbgEntry and dbgExit statements must be on separate lines.
If InStr(myLines(nCurrLine), "dbgEntry (") Or InStr(myLines(nCurrLine), "dbgExit (") Then
nNewLine = nNewLine ' do nothing
Else ' we have an old dbgEntry or dbgExit lines, just drop it
If bInstrumentRoutine Then
If (nNewLine + 2) >= myNewLinesSize Then
myNewLinesSize = myNewLinesSize + 50
ReDim Preserve myNewLines(myNewLinesSize)
End If
nWhereFound = InStr(myLines(nCurrLine), sExitString)
If nWhereFound Then
myNewLines(nNewLine) = Space(nWhereFound - 1) & "dbgExit (""" & pModuleName & "." & pRoutineName & """)"
myNewLines(nNewLine + 1) = myLines(nCurrLine)
nNewLine = nNewLine + 2
ElseIf Left(myLines(nCurrLine), sEndStringSize) = sEndString Then
myNewLines(nNewLine) = " dbgExit (""" & pModuleName & "." & pRoutineName & """)"
myNewLines(nNewLine + 1) = myLines(nCurrLine)
nNewLine = nNewLine + 2
Else ' nothing special, just copy over
myNewLines(nNewLine) = myLines(nCurrLine)
nNewLine = nNewLine + 1
End If
Else ' not instrumenting, just copy over
myNewLines(nNewLine) = myLines(nCurrLine)
nNewLine = nNewLine + 1
End If
End If
Next 'grab next line
'add what we have into the myNewCode string
ReDim Preserve myNewLines(nNewLine) ' truncate the array to just those that we filled
myNewCode = myNewCode & Join(myNewLines, vbCrLf)
End Sub
Private Function fMax(pLeft As Variant, pRight As Variant) As Variant
If pLeft > pRight Then fMax = pLeft Else fMax = pRight
End Function
Private Function fMin(pLeft As Variant, pRight As Variant) As Variant
If pLeft < pRight Then fMin = pLeft Else fMin = pRight
End Function
Private Function fArraySize(pArray)
fArraySize = UBound(pArray) - LBound(pArray) + 1
End Function