Home > Back-end >  How do I parse VBA code line instructions
How do I parse VBA code line instructions

Time:03-12

I am trying to create a VBA function that parses VBA code. I'm at the stage where I'm trying to put in an array variable all the instructions present on a line of code. For example the following code contains two instructions:

strVar = "Some text"
lngVar = 2

Those 2 instructions can also be written as following:

strVar = "Some text": lngVar = 2

I specify that I already have a personal function that transforms a "multiline" line into a simple line:

strVar = "From text": _
  lngVar = 2 'Multiline'

So the CodeLine argument (the line of code to be parsed) of my custom function always contains a single code line. I thought I had reached the desired result since I get the right result with some twisted lines such as :

str = " : 1": str = " : 2": str = " : 2"

or

str = """ : 1""": str = """ : 2""": str = """ :"" 2""

But I realize that I don't get the desired result for this kind of lines:

Next vntSubString: CommentPosition = IIf(blnStringMode, 0, InStr(1, CodeLine, ": "))

I'd really like to do something generic and quick to execute, I think I'm not too far from the final result, but I'm a bit blocked.

Here's what my personal function looks like at the moment:

Public Function SplitInstructions(ByVal CodeLine As String) As Variant()

  Dim vntResult() As Variant
  Dim vntSubString¹ As Variant
  Dim blnIsStringMode As Boolean 'Determines if we are in a subtext between quotes or not
  Dim vntSubString² As Variant

  Let vntResult = VBA.Array

  If InStr(1, CodeLine, ": ") = 0 Then 'A single instruction
    Let vntResult = VBA.Array(CodeLine)
  ElseIf InStr(1, CodeLine, """") = 0 Then 'Several statements, but no quotes => On Split
    Do Until VBA.InStr(1, CodeLine, "::") = 0
      Let CodeLine = VBA.Replace(CodeLine, "::", ":")
    Loop: Call AddToArray(vntResult, Split(CodeLine, ":"))
  Else 'it gets complicated
    For Each vntSubString¹ In Split(CodeLine, """")
      If blnIsStringMode Then
        Let vntResult(UBound(vntResult)) = Trim$(vntResult(UBound(vntResult)) & """" & vntSubString¹ & """")
      Else
        For Each vntSubString² In Split(vntSubString¹, ": ")
          If vntSubString² <> vbNullString Then Call AddToArray(vntResult, vntSubString²)
        Next vntSubString²
      End If
      Let blnIsStringMode = Not blnIsStringMode
    Next vntSubString¹
  End If

  Let SplitInstructions = vntResult

End Function

Private Sub AddToArray(ByRef Arr() As Variant, ByVal Value As Variant)

  Dim vntValue As Variant

  If VBA.IsArray(Value) Then
    For Each vntValue In Value
      Call AddToArray(Arr, vntValue)
    Next vntValue
  Else
    ReDim Preserve Arr(LBound(Arr) To UBound(Arr)   1)
    Let Arr(UBound(Arr)) = Value
  End If

End Sub

In advance, thank you for your help!

Edit : here is the function I use to determine then comment position of a code line

Private Function CommentPosition(ByVal CodeLine As String) As Long

  Dim vntSubString As Variant
  Dim blnStringMode As Boolean
  Dim x As Long

  For Each vntSubString In VBA.Split(CodeLine, """")
    If Not blnStringMode Then
      Let x = VBA.InStr(1, vntSubString, "'")
      If x > 0 Then
        Let CommentPosition = CommentPosition   x
        Exit Function
      End If
    End If
    Let blnStringMode = Not blnStringMode
    Let CommentPosition = CommentPosition   VBA.Len(vntSubString)   1
  Next vntSubString

  Let CommentPosition = VBA.IIf(blnStringMode, 0, VBA.InStr(1, CodeLine, "'"))

End Function

CodePudding user response:

If you want to split on double quotes, you'd need to determine if you're inside a set of parentheses and keep all of that together. I think that's a lot of work. What if you just iterate through the string sequentially?

Public Function SplitInstructions2(ByVal CodeLine As String) As String()
    
    Dim i As Long
    Dim lLastPos As Long
    Dim aReturn() As String
    Dim bInString As Boolean
    Dim lCnt As Long
    
    ReDim aReturn(1 To 1000)
    lLastPos = 1
    
    For i = 1 To Len(CodeLine)
        If Mid$(CodeLine, i, 1) = ":" And Not bInString Then
            lCnt = lCnt   1
            aReturn(lCnt) = Trim$(Mid$(CodeLine, lLastPos, i - lLastPos))
            lLastPos = i   1
        ElseIf Mid$(CodeLine, i, 1) = """" Then
            bInString = Not bInString
        End If
    Next i
    
    lCnt = lCnt   1
    aReturn(lCnt) = Trim$(Mid$(CodeLine, lLastPos, Len(CodeLine) - lLastPos   1))
    
    ReDim Preserve aReturn(1 To lCnt)
    
    SplitInstructions2 = aReturn
    
End Function

CodePudding user response:

Building on Dick's answer to parse the string, but utilising InStr to look ahead to the next character of interest:

Sub test()
    Dim CodeLine As String
    Dim CodeLines() As String
    
    CodeLine = "str = """""" : 1"""""": str = """""" : 2"""""": str = """""" :"""" 2"""""

    CodeLines = SplitInstructions(CodeLine)
    
    Stop
End Sub

Function SplitInstructions(ByVal CodeLine As String) As String()
    Dim CharOfInterest As String
    Dim idx As Long
    Dim aReturn() As String
    Dim NumLines As Long
    
    ReDim aReturn(1 To 1000)
    NumLines = 1
    aReturn(1) = CodeLine
    idx = 1
    Do
        Debug.Print aReturn(NumLines), idx
        CharOfInterest = GetNextCharOfInterest(aReturn(NumLines), idx)
        Select Case CharOfInterest
            Case """"
                ' Ignore remainder of quoted string
                idx = GetStringClose(aReturn(NumLines), idx)   1
            Case ":"
                ' Break on :
                aReturn(NumLines   1) = Trim$(Mid$(aReturn(NumLines), idx   1))
                aReturn(NumLines) = Trim$(Left$(aReturn(NumLines), idx - 1))
                NumLines = NumLines   1
                idx = 1
            Case "'", vbNullString
                ' Comment, or end of code
                ReDim Preserve aReturn(1 To NumLines)
                Exit Do
        End Select
    Loop
    SplitInstructions = aReturn
End Function

' Look ahead to end of Quoted string
Function GetStringClose(CodeLine As String, ByRef idx As Long)
    Dim i As Long
    If Mid$(CodeLine, idx, 1) = """" Then 'verfiy
        i = InStr(idx   1, CodeLine, """")
        Do
            If Mid$(CodeLine, i   1, 1) = """" Then
                ' delimited "
                i = i   1
                i = InStr(i   1, CodeLine, """")
            Else
                ' end of quoted string
                i = IIf(i = 0, Len(CodeLine)   1, i)
                GetStringClose = i
                Exit Do
            End If
        Loop
    Else
        'invalid call
        Stop
    End If
End Function


Function GetNextCharOfInterest(CodeLine As String, idx As Long) As String
    Dim Quote As Long
    Dim Colon As Long
    Dim Comment As Long
    Dim MinPos As Long
    
    If idx > Len(CodeLine) Then
        GetNextCharOfInterest = vbNullString
        Exit Function
    End If
    Quote = InStr(idx, CodeLine, """")
    Colon = InStr(idx, CodeLine, ":")
    Comment = InStr(idx, CodeLine, "'")
    
    If Quote   Colon   Comment = 0 Then
        GetNextCharOfInterest = vbNullString
    Else
        Quote = IIf(Quote = 0, Len(CodeLine)   1, Quote)
        Colon = IIf(Colon = 0, Len(CodeLine)   1, Colon)
        Comment = IIf(Comment = 0, Len(CodeLine)   1, Comment)
        
        MinPos = Application.Min(Quote, Colon, Comment)
        If Quote = MinPos Then
            GetNextCharOfInterest = """"
            idx = Quote
        ElseIf Colon = MinPos Then
            GetNextCharOfInterest = ":"
            idx = Colon
        Else
            GetNextCharOfInterest = "'"
            idx = Comment
        End If
    End If
End Function

The test result

enter image description here

  • Related