How Do I Reverse Engineer a File Using VBA?
WithClass 99 has a powerful built in parser that lets you do some raw parsing to aid you in bringing software architecture into a WithClass diagram. Below is an example of a subroutine that reverse engineers a PERL file into a class. The same model could be used to reverse engineer Smalltalk,
Eiffel, and PowerBuilder and other OO languages not handled by WithClass 99. Basically, the subroutine is a kind of parsing state machine. The structure of this machine is a select statement embedded in a while loop. The while loop fetches the new token each time through the loop and then enters the particular state that the file is in at a particular token. The With_Class.Parser object is used to retrieve each token, search for new tokens and test upcoming tokens.
The matchbrace function below the perlparse function is used to extract code from a perl subroutine. It matches brackets {} to the end of the subroutine so that the entire subroutine is extracted
Listing 1.
This subroutine gets the file from the user and calls perlparse to parse the file
Public Sub ReversePerlFile()
Dim theFile As String
theFile = VBA.InputBox("Enter file to reverse ")
perlparse (theFile)
MsgBox ("Finished Reversing " & theFile)
End Sub
This subroutine reverses a perl file into a class extracting class name, base class and methods.
It also attempts to connect generalizations to the classes in the file when its finished.
Sub perlparse(theFile As String)
On Error GoTo ErrorHandler
Different states of the state machine
Const IdleState = 1
Const OperationState = 2
Const BaseState = 3
Const CodeState = 4
Const ClassNameState = 5
Const ClassFinishedState = 6
Const PackageNameState = 7
Declaration of WithClass Objects and other variables
Dim aParser As New With_Class.Parser
Dim wcDocument As With_Class.Document
Dim theClass As With_Class.Class
Dim thePackage As With_Class.Package
Dim TheClasses As With_Class.Classes
Dim theOperation As With_Class.Operation
Dim nextToken As String
Dim state As Integer
Dim xSpace As Integer
Dim tmpStr As String
Dim classStr As String
Dim packageStr As String
Dim lenfile As Long
Used to space the classes initially, not really necessary since we will call
ArrangeClasses in the document
xSpace = 100
state = IdleState
Set wcDocument = With_Class.ActiveDocument
'theFile = "c:\temp\Perl\test\search.pm"
lenfile = FileLen(theFile)
aParser.WriteBufferFromFile (theFile)
nextToken = aParser.GetNextNonSpaceToken
While (aParser.IsLastToken = False)
Select Case state
Case IdleState
Search for class name
If (nextToken = "package") Then
state = ClassNameState
End If
search for inheritance
If (nextToken = "@") Then
nextToken = aParser.GetNextNonSpaceToken
If nextToken = "ISA" Then
state = BaseState
End If
End If
search for method of current class
If (nextToken = "sub") Then
' found operation
state = OperationState
End If
Case ClassNameState
If (aParser.IsNextToken(":")) Then
packageStr = nextToken
state = PackageNameState
Else
classStr = nextToken
state = ClassFinishedState
End If
Case PackageNameState
If (nextToken = ":") Then
packageStr = packageStr + nextToken
Else
If (aParser.IsNextToken(":") = False) Then
classStr = nextToken
state = ClassFinishedState
Else
packageStr = packageStr + nextToken
End If
End If
Case ClassFinishedState
If (Len(packageStr) > 0) Then
' strip off last colons
While (Mid(packageStr, Len(packageStr), 1) = ":")
packageStr = Left(packageStr, Len(packageStr) - 1)
Wend
End If
create the new class and write it to the ActiveDocument
Set theClass = wcDocument.NewClass(xSpace, 100, classStr)
theClass.ImportFileName = theFile
theClass.filename = theFile
If (Len(packageStr) > 0) Then
Set thePackage = wcDocument.NewPackage(200, xSpace, packageStr)
thePackage.AddClass theClass
End If
theClass.Refresh
xSpace = xSpace + theClass.Width + 20
state = IdleState
Case BaseState
If (nextToken = "qw") Then
ElseIf (Len(nextToken) > 1) Then
theClass.LibraryBaseClass = nextToken
End If
If (nextToken = ";") Then
state = IdleState
End If
Case OperationState
If (nextToken = "new") Then ' constructor
nextToken = theClass.Name
End If
create a new operation in the current class
Set theOperation = theClass.NewOperation(nextToken)
state = CodeState
Case CodeState
' tmpString = aParser.GetLineToToken("}")
If (nextToken <> "{") Then
aParser.GotoToken ("{")
End If
tmpString = MatchBrace(aParser)
theOperation.Code = tmpString
state = IdleState
Case Else
End Select
nextToken = aParser.GetNextNonSpaceToken
Wend
' Now arrange the classes
Set TheClasses = wcDocument.Classes
TheClasses.Restart
While (TheClasses.IsLast = False)
Set theClass = TheClasses.GetNext
If (Len(theClass.LibraryBaseClass)) Then
Set theBaseClass = TheClasses.FindClass(theClass.LibraryBaseClass)
If (IsNull(theBaseClass) = False) Then
wcDocument.NewGeneralization theBaseClass, theClass
End If
End If
Wend
wcDocument.ArrangeClasses
wcDocument.ArrangeRelationships
Exit Sub
ErrorHandler:
sMsg = "Error # " & Str(Err.Number) & " was generated by " & Err.Source & Chr(13) & Err.Description
MsgBox sMsg, , "Error", Err.HelpFile, Err.HelpContext
End Sub
Match the braces and the current point of the parser
Function MatchBrace(aParser As With_Class.Parser) As String
Dim startParse As Long
Dim endParse As Long
Dim braceCount As Long
Dim nextToken As String
startParse = aParser.Ptr
braceCount = 1
While ((braceCount > 0) And (aParser.IsLastToken = False))
nextToken = aParser.GetNextNonSpaceToken
If (nextToken = "{") Then
braceCount = braceCount + 1
End If
If (nextToken = "}") Then
braceCount = braceCount - 1
End If
Wend
startParse = startParse + 1 ' get rid of first brace
endParse = aParser.Ptr
MatchBrace = Mid(aParser.Buffer, startParse, endParse - startParse)
End Function