Search code examples
excelvbapretty-print

How to add spaces to pretty print pseudo-code?


I'm trying to indent a pseudo-code that I have column A and is not indented. Basically I'm trying to add 4 spaces when is time to do it even is there is a nested if/elseif/else/end block or while/end block

This is my input:

WHILE TRUE
DISPLAY "Welcome to Bank ATM - Cash Withdrawal"
amount = INPUT "How much would you like to withdraw today?"
IF (amount MOD 10) != 0 THEN
DISPLAY "You can only withdraw a multiple of ten!"
ELSE
IF amount<10  THEN
DISPLAY "You can withdraw only above of £10"
ELSEIF amount>1000 THEN
DISPLAY "You can only withdraw between £10 and £1000"
ELSE
notes20 = amount DIV 20
notes10 = (amount MOD 20) / 10
DISPLAY "Collect your money: "
DISPLAY "    >> £20 Banknotes: " + notes20
DISPLAY "    >> £10 Banknotes: " + notes10
DISPLAY "Thank you for using this Python Bank ATM."
DISPLAY "Good Bye."
END
END
END

And this is my current attempt that currently is printing exactly the same as input.

Sub indent()

a = WorksheetFunction.Transpose(Range("A1:A21"))

if_count = 0
end_count = 0
For i = 1 To UBound(a)
    If a(i) = "IF" Then
        If (if_count = 0 And end_count = 0) Then
            Debug.Print a(i)
            if_count = if_count + 1
        ElseIf if_count > 0 Then
            Debug.Print "    " & a(i)
            if_count = 0
        Else
            Debug.Print a(i)
        End If
    Else
        Debug.Print a(i)
    End If
Next

End Sub

The output I'm looking for is like this:

WHILE TRUE
   DISPLAY "Welcome to Python Bank ATM - Cash Withdrawal"
   amount = INPUT "How much would you like to withdraw today?"
   IF (amount MOD 10) != 0 THEN
      DISPLAY "You can only withdraw a multiple of ten!"
   ELSE
      IF amount<10  THEN
         DISPLAY "You can withdraw only above of £10"
      ELSEIF amount>1000 THEN
         DISPLAY "You can only withdraw between £10 and £1000"
      ELSE
         notes20 = amount DIV 20
         notes10 = (amount MOD 20) / 10
         DISPLAY "Collect your money: "
         DISPLAY "    >> £20 Banknotes: " + notes20
         DISPLAY "    >> £10 Banknotes: " + notes10
         DISPLAY "Thank you for using this Python Bank ATM."
         DISPLAY "Good Bye."
      END
   END
END

In image would look like this: enter image description here

Thanks for any help.


Solution

  • Just as addition to Taller's valid & clever solution, I want to demonstrate how to organize a dictionary storing the integrated logic separately; this is done via 4 pairs only of positive or negative level increments based on predefined code keywords like e.g. "IF", "ELSE", "END"... etc. Thus each dictionary call returns an increment (or decrement) to the current code hierarchy level.

    The main routine Indent consists of the following steps:

    • A) Assign range content (showing pseudo code) to 2D datafield array.
    • B) Pad lines within the code array via loop first and ..
    • C) Write the entire code array to target range eventually.

    Functions

    A certain challenge was to outsource B's padding routine per keyword as separate function Pad() and to provide it with predefined level increments via dictionary Incr - c.f. Pad section 1).

    The relevant parts therein are:

      1. Prefill dictionary incr with level increments (Set incr = LoadIncr(prefix)).
      1. Adapt the passed keyword by a given prefix.
      1. Return the correct keyword padding via a Static level variable lvl.

    Prefilling the dictionary via helper Function LoadIncr() associates predefined keywords like "IF", "WHILE", "ELSE",.. etc. with a couple of fixed level increments changing the hierarchy level individually.


    Main routine Indent

    Option Explicit         ' head of code module
    
    'Site: https://stackoverflow.com/questions/78332116/how-to-add-BLANKs-to-pretty-print-pseudo-code/78332183#78332183
    'Auth: https://stackoverflow.com/users/6460297/t-m
    'Date: 2024-04-16
    Sub Indent()
    With Sheet1              ' << qualify worksheet reference according to your needs
        Dim lastRow As Long: lastRow = .Range("A" & .Rows.Count).End(xlUp).Row
    'A) Assign Range content (pseudo code) to 2D datafield array
        Dim code As Variant:  code = .Range("A1:A" & lastRow).Value2
    'B) Pad lines within the code array first
        Dim i As Long
        For i = 1 To UBound(code)
            Dim keyword As String: keyword = UCase(Split(code(i, 1))(0))
            ' >> Apply padding upon code elements<<
            code(i, 1) = pad(keyword) & code(i, 1)  ' prepend padding to original code string
        Next
    'C) Write entire code array to target range (or VBE's Immediate Window) eventually
        .Range("B1").Resize(UBound(code), 1).Value = code
        Debug.Print Join(WorksheetFunction.Transpose(code), vbNewLine)
    End With
    End Sub
    

    Function Pad()

    Function pad(ByVal keyword As String, Optional cnt As Long = 4) As String
        Const prefix As String = "$"
    '1) Prefill dictionary with level increments
        Dim incr As Dictionary
        Set incr = LoadIncr(prefix)
    '2) Adapt keyword with a given prefix
        keyword = prefix & keyword                          ' add prefix for search
        If Not incr.exists(keyword) Then keyword = prefix   ' no predefined keyword
    '3) Return correct keyword padding
        Static lvl As Long                                  ' remember indent level
        lvl = lvl + incr(keyword)(0)
        pad = String(lvl * cnt, " ")                        ' return padded line
        lvl = lvl + incr(keyword)(1)
    End Function
    

    Helper function LoadIncr()

    Function LoadIncr(Optional ByVal prefix As String = "$") As Dictionary
    'Purp:  Add predefined keys and increments to dictionary and return it as a whole
    '1) Set Dictionary to memory
        Dim myDict As Object        ' late binding
        Set myDict = CreateObject("Scripting.Dictionary")
    '2) Add 4 predefined key types and couples of level increments (before|after padding)
        Dim myKey As Variant
        'a) Start keys
            For Each myKey In Array("IF", "WHILE")
                myDict.Add prefix & myKey, Array(0, 1)
            Next
        'b) Mid keys
            For Each myKey In Array("ELSE", "ELSEIF")
                myDict.Add prefix & myKey, Array(-1, 1)
            Next
        'c) End keys
            For Each myKey In Array("END", "WEND")
                myDict.Add prefix & myKey, Array(-1, 0)
            Next
        'd) All Others (i.e. in between above predefined keys)
            myDict.Add prefix & "", Array(0, 0)
            
    '3) Return Dictionary (including predefined level increments) as Function result
        Set LoadIncr = myDict
    End Function