Results 1 to 18 of 18

Thread: String Interpolation, String Formatting, Composite Formatting, Template Literals

  1. #1

    Thread Starter
    Junior Member
    Join Date
    Dec 2024
    Posts
    23

    Question String Interpolation, String Formatting, Composite Formatting, Template Literals

    Hi! Seniors,

    Please suggest good libs/code for VB6/VBA and should be competitive with following languages.

    Code:
    C# (.NET) Composite Formatting String.Format("{0}, {1}", "Hello", "World")
    String Interpolation ($"...") $"Hello, {name}!"
    Python String Formatting (str.format) "Hello, {}!".format("World")
    f-strings (String Interpolation, Python 3.6+) f"Hello, {name}!"
    Java Formatting with String.format String.format("%s, %s", "Hello", "World")
    JavaScript Template Literals `Hello, ${name}!`
    String Concatenation (Basic) "Hello, " + name + "!"
    Ruby Interpolation with #{} "Hello, #{name}!"
    sprintf or format sprintf("Hello, %s!", name)
    PHP Double-Quoted Strings with Variables "Hello, $name!"
    sprintf sprintf("Hello, %s!", $name)
    C++ printf/sprintf sprintf(buffer, "Hello, %s!", name);
    String Interpolation (C++20, std::format) std::format("Hello, {}!", name)
    Go fmt.Sprintf fmt.Sprintf("Hello, %s!", name)
    Rust Formatting Macros format!("Hello, {}!", name)
    Kotlin String Templates "Hello, $name!"
    String.format (Java-style) "Hello, %s!".format(name)
    Swift String Interpolation print("Hello, \(name)!")
    Perl String Interpolation (Double-Quoted Strings) "Hello, $name!"
    sprintf sprintf("Hello, %s!", $name)
    Dart String Interpolation 'Hello, $name!'
    Lua String Formatting string.format("Hello, %s!", name)
    Objective-C String Formatting with NSString [NSString stringWithFormat:@"Hello, %@!", name]
    Shell Scripts Interpolation (Double-Quoted Strings) "Hello, $name!"
    R sprintf sprintf("Hello, %s!", name)
    Thank you!

  2. #2
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,841

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    I'm using something simple like this:

    Code:
    Public Function Printf(ByVal sText As String, ParamArray A() As Variant) As String
        Const LNG_PRIVATE   As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
        Dim lIdx            As Long
        
        For lIdx = UBound(A) To LBound(A) Step -1
            sText = Replace(sText, "%" & (lIdx - LBound(A) + 1), Replace(A(lIdx), "%", ChrW$(LNG_PRIVATE)))
        Next
        Printf = Replace(sText, ChrW$(LNG_PRIVATE), "%")
    End Function
    . . . to format with %1, %2, %3, etc. placeholders like this:

    > Debug.Print Printf("Hello, %1!", "World")
    > Hello, World!

    cheers,
    </wqw>

  3. #3

    Thread Starter
    Junior Member
    Join Date
    Dec 2024
    Posts
    23

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Hello wqweto sir,

    missing so many things like format, special characters, escaping etc. its ok for learn but need more advance and complex like .Net and others

    thank you so much for your reply and for your work

  4. #4
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,725

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Here's my shot at an (optionally) recursive string formatted. Note that it's barely tested so there will be bugs, and it has not be optimized at all so it will probable perform poorly on large strings/lots of recursion.

    Code:
    Public Function RPrintF(ByVal p_String As String, ParamArray p_SubStrings() As Variant) As String
       Const c_MaxRecurse As Long = 0   ' Set to 0 to disable "pseudo-recursive" substitutions,
                                        ' or set it to a reasonable number if you want "pseudo-recursive" replacement of %# substrings (with infinite loop protection).
         
       Dim ii As Long
       Dim l_Char As String
       
       Dim l_InSubStringDef As Long
       Dim l_InSubStringIndex As Long
       Dim l_InSubStringFormatDef As Long
       
       Dim l_RecurseCount As Long
       Dim l_RecurseEnd As Long
       
       Dim l_SubStringIndex As Long
       Dim l_FormatString As String
       Dim l_FormatStringDef As String
       
       If (UBound(p_SubStrings) >= 0) And _
          (Len(p_String) > 1) Or _
          (InStr(1, p_String, "%") > 0) Then  ' Only scan for substitutions if there is string content to scan,
                                              ' substrings to substitute,
                                              ' or if there is a % character (we may need to unescape even if there are no substitution strings)
          
          ii = 1
          
          Do While ii <= Len(p_String) + 1
             If ii <= Len(p_String) Then
                l_Char = Mid$(p_String, ii, 1)
             Else
                l_Char = vbNullString
             End If
             
             Select Case l_Char
             Case "%"
                ' Check for start of substitution index marker
                
                If l_InSubStringFormatDef = 0 Then
                   If (l_InSubStringDef > 0) And (l_InSubStringDef = ii - 1) Then
                      l_InSubStringDef = 0
                      
                      p_String = Left$(p_String, ii - 1) & Mid$(p_String, ii + 1)
                      ii = ii - 1
                   Else
                      l_InSubStringDef = ii
                   End If
                End If
                
             Case "0" To "9"
                ' Check for substitution index #
                
                If l_InSubStringFormatDef = 0 Then
                   If l_InSubStringDef Then
                      l_InSubStringIndex = ii
                   Else
                      l_InSubStringIndex = 0
                   End If
                End If
                
             Case Else
                Select Case l_Char
                Case "{"
                   ' Check for start of format marker
                   
                   If l_InSubStringFormatDef = 0 Then
                      If l_InSubStringIndex > 0 Then
                         l_InSubStringFormatDef = ii
                      End If
                   ElseIf l_InSubStringFormatDef = ii - 1 Then
                      l_InSubStringFormatDef = 0
                   End If
                   
                Case "}"
                   ' Check for end of format marker
                   
                   If l_InSubStringFormatDef > 0 Then
                      If Mid$(p_String, ii + 1, 1) <> "}" Then
                         ' Found end of format string
                         l_FormatString = Mid$(p_String, l_InSubStringFormatDef + 1, ii - l_InSubStringFormatDef - 1)
                         l_FormatStringDef = "{" & l_FormatString & "}"
                         l_InSubStringFormatDef = 0
                      End If
                   End If
                End Select
                
                If (l_InSubStringIndex > 0) And _
                   (l_InSubStringDef > 0) And _
                   (l_InSubStringFormatDef = 0) Then
                   
                   ' Perform substitution
                   
                   l_SubStringIndex = Mid$(p_String, l_InSubStringDef + 1, l_InSubStringIndex - l_InSubStringDef)
                   
                   If l_SubStringIndex > 0 And l_SubStringIndex <= UBound(p_SubStrings) + 1 Then
                      l_RecurseEnd = l_InSubStringDef + Len(Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString))
                      l_RecurseCount = l_RecurseCount + 1
                                     
                      p_String = Mid$(p_String, 1, l_InSubStringDef - 1) & Replace$(p_String, "%" & l_SubStringIndex & l_FormatStringDef, Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString), l_InSubStringDef, 1)
                   
                      ii = l_InSubStringDef - 1
                   End If
                   
                   l_FormatString = vbNullString
                   l_FormatStringDef = vbNullString
                   
                   l_InSubStringIndex = 0
                   l_InSubStringDef = 0
                   l_InSubStringFormatDef = 0
                
                ElseIf l_InSubStringFormatDef = 0 Then
                   l_InSubStringDef = 0
                
                End If
             End Select
                      
             ii = ii + 1
             
             ' Infinite loop protection
             If l_RecurseEnd > 0 Then If ii > l_RecurseEnd Then l_RecurseCount = 0: l_RecurseEnd = 0
             If l_RecurseCount > c_MaxRecurse Then ii = l_RecurseEnd + 1
          Loop
       End If
       
       RPrintF = p_String
    End Function

    Some notes:

    • Substitution indexes are identified by a leading % character, and are 1-based. For example, RPrintF("Print %1", "Test") will output Print Test.

    • You can add a format string after any substitution identifier by enclosing it in {braces}. Format strings can be anything that the VB6 Format function accepts. For example, RPrintF("Today is %1{Short Date}", Now) will return Today is 2025/03/17 (or whatever your system's short date format is)

    • Substitution indexes do not have to be in order, so RPrintF("%2 %1", "B", "A") will work and return A B

    • Recursion is supported with infinite loop protection if you set the c_MaxRecurse constant > 0. If you enable recursion, then something like this: RPrintF("Let's do %1.", "something %3", "terrible", "exciting") will return Let's do something exciting.

    • If you mess up and try to do something like RPrintF("This is an infinite loop bomb %1", "Infinite Loop Bomb %2", "Infinite Loop Bomb %1") with recursion enabled you will get a crazy result, but you won't lock the program.

    • You can escape special characters by doubling them up. For example %% will escape % so it doesn't get misinterpreted as the start of a sub-string identifier.


    Hope it comes in handy, but please remember that the code has barely been tested so please report any problems here (or feel free to fix them and post the results here).
    Last edited by jpbro; Mar 19th, 2025 at 02:45 PM. Reason: Hardened against some bad inputs

  5. #5
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    8,792

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by silver-surfer View Post
    Hi! Seniors,
    Please suggest good libs/code for VB6/VBA and should be competitive with following languages.
    Just be aware that some of these features are compiler features. For example, no amount of VB6 code will give you string interpolation. That has to be implemented in the compiler.
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena

    Copy/move files using Windows Shell | I'm not wanted

    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. - jmcilhinney

    The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber

  6. #6
    PowerPoster
    Join Date
    Aug 2010
    Location
    Canada
    Posts
    2,725

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by Niya View Post
    Just be aware that some of these features are compiler features. For example, no amount of VB6 code will give you string interpolation. That has to be implemented in the compiler.
    Good point, at best you'll get a "poor man's" string interpolation from VB6. IIRC twinBasic offers "real" string interpolation (or it's planned at least) if you OP wants to try a modern more VB6-like replacement to vb.Net.

  7. #7
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,841

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by silver-surfer View Post
    Hello wqweto sir,

    missing so many things like format, special characters, escaping etc. its ok for learn but need more advance and complex like .Net and others

    thank you so much for your reply and for your work
    Do these outside your Printf usage e.g. sMsg = Printf("Current Date is %1", Format$(Date, FORMAT_SHORT_DATE)) where FORMAT_Xxx constants are project specific custom formats.

    It's like when you need to change date format midway project (only dates, not currency, weight or volume) to something else so now someone will have to go find and fix all those String.Format specifiers and miss a bunch.

    I prefer to keep *any* advanced stuff out of my trusty Printf so it can be used in critical sutiations (like error handling) with strong confidence it cannot fail. Advanced formatting, escaping, etc. is left to Format function and/or additional helpers like Pad, Trim, ToHex, ToBase64, ToUtf8, etc.

    cheers,
    </wqw>

  8. #8
    Fanatic Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    674

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by wqweto View Post
    I'm using something simple like this:

    Code:
    Public Function Printf(ByVal sText As String, ParamArray A() As Variant) As String
        Const LNG_PRIVATE   As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
        Dim lIdx            As Long
        
        For lIdx = UBound(A) To LBound(A) Step -1
            sText = Replace(sText, "%" & (lIdx - LBound(A) + 1), Replace(A(lIdx), "%", ChrW$(LNG_PRIVATE)))
        Next
        Printf = Replace(sText, ChrW$(LNG_PRIVATE), "%")
    End Function
    . . . to format with %1, %2, %3, etc. placeholders like this:

    > Debug.Print Printf("Hello, %1!", "World")
    > Hello, World!

    cheers,
    </wqw>
    This is a handy little routine. Here's a more robust version with some error handling and comments...

    Code:
    Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
        ' Define a constant for a Unicode Private Use Area character to temporarily replace % in arguments
        Const TEMP_PERCENT As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
        Dim argIndex As Long    ' Variable to loop through the argument array
        
        ' Enable error handling to catch and report any runtime issues
        On Error GoTo ErrHandler
        
        ' Check if the input format string is empty; if so, return an empty string
        If Len(formatString) = 0 Then
            Printf = vbNullString
            Exit Function
        End If
        
        ' Check if no arguments were provided (empty ParamArray); if so, return the format string unchanged
        If UBound(args) < LBound(args) Then
            Printf = formatString
            Exit Function
        End If
        
        ' Loop through the arguments in reverse order (from highest index to lowest)
        For argIndex = UBound(args) To LBound(args) Step -1
            ' Replace the placeholder (e.g., %1, %2) in the format string with the current argument
            ' First, replace any % in the argument with TEMP_PERCENT to avoid conflicts
            ' Then, substitute the placeholder with the modified argument
            formatString = Replace(formatString, "%" & (argIndex - LBound(args) + 1), _
                                  Replace(args(argIndex), "%", ChrW$(TEMP_PERCENT)))
        Next
        
        ' After all placeholders are replaced, restore any original % characters by
        ' replacing TEMP_PERCENT back to % in the final string
        Printf = Replace(formatString, ChrW$(TEMP_PERCENT), "%")
        
        ' Exit the function normally
        Exit Function
    
    ErrHandler:
        ' If an error occurs, return an error message with the description
        Printf = "[Error in Printf: " & Err.Description & "]"
    End Function

  9. #9
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,841

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    @AAraya: Nice work, but you cannot use procedures with On Error ... statements while handling errors because Err object gets reset.

    I mean if you have something like this

    Code:
        On Error GoTo ErrHandler
        ...
        Exit Sub
    ErrHandler:
        Debug.Print Printf("Error: %1", Err.Description)
        Debug.Print "Ooops, Err object is cleared at this point because Printf body has On Error statement"
        LogError "MyModule::MyFunction"
    End Sub
    This is the only reason original procedure does not have error handling already.

    cheers,
    </wqw>

  10. #10
    Fanatic Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    674

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    @wqweto - good point! Here's the function with the On Error functionality removed but with the validations for format string and ParamArray arguments.

    Code:
    Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
        ' Define a constant for a Unicode Private Use Area character to temporarily replace % in arguments
        Const TEMP_PERCENT As Long = &HE1B6& '-- U+E000 to U+F8FF - Private Use Area (PUA)
        Dim argIndex As Long    ' Variable to loop through the argument array
        
        ' Check if the input format string is empty; if so, return an empty string
        If Len(formatString) = 0 Then
            Printf = vbNullString
            Exit Function
        End If
        
        ' Check if no arguments were provided (empty ParamArray); if so, return the format string unchanged
        If UBound(args) < LBound(args) Then
            Printf = formatString
            Exit Function
        End If
        
        ' Loop through the arguments in reverse order (from highest index to lowest)
        For argIndex = UBound(args) To LBound(args) Step -1
            ' Replace the placeholder (e.g., %1, %2) in the format string with the current argument
            ' First, replace any % in the argument with TEMP_PERCENT to avoid conflicts
            ' Then, substitute the placeholder with the modified argument
            formatString = Replace(formatString, "%" & (argIndex - LBound(args) + 1), _
                                  Replace(args(argIndex), "%", ChrW$(TEMP_PERCENT)))
        Next
        
        ' After all placeholders are replaced, restore any original % characters by
        ' replacing TEMP_PERCENT back to % in the final string
        Printf = Replace(formatString, ChrW$(TEMP_PERCENT), "%")
    
    End Function

  11. #11
    PowerPoster wqweto's Avatar
    Join Date
    May 2011
    Location
    Sofia, Bulgaria
    Posts
    5,841

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Btw, not sure ChatGPT added something useful here provided that it missed to include raising exception when formatString contains TEMP_PERCENT i.e. it couldn't figure out this is unsupported case and a much better/real input check than currently optimizing on empty formatString or missing placeholder replacements.

    cheers,
    </wqw>

  12. #12
    Fanatic Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    674

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by wqweto View Post
    Btw, not sure ChatGPT added something useful here provided that it missed to include raising exception when formatString contains TEMP_PERCENT i.e. it couldn't figure out this is unsupported case and a much better/real input check than currently optimizing on empty formatString or missing placeholder replacements.

    cheers,
    </wqw>
    That seems to be a rare edge case since TEMP_PERCENT is a Private Use Area character unlikely to appear in typical input. How likely do you consider this situation to be? To my mind empty strings and missing placeholders are more likely... But for bulletproof robustness I suppose that would be a good enhancement.

    Rather than using a fixed/constant PUA character we could iterate the PUA until we find one that doesn't appear in the input string?

    Code:
    Private Function FindUnusedPUAChar(ByVal str As String) As Long
        Dim codePoint As Long
        For codePoint = &HE000& To &HF8FF& ' PUA range
            If InStr(str, ChrW$(codePoint)) = 0 Then
                FindUnusedPUAChar = codePoint
                Exit Function
            End If
        Next
        ' If no unused character is found (extremely unlikely), raise an error
        Err.Raise vbObjectError + 1002, "Printf", "No unused Private Use Area character available"
    End Function
    Last edited by AAraya; Mar 18th, 2025 at 12:03 PM.

  13. #13
    Fanatic Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    674

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    This started out to be an interesting thought experiment for me but then I realized that I was doing a simple version of this manually in a couple places in my code - mainly with resource strings that contained placeholders (%1, %2, etc). So I created a Printf function based on some of the things discussed in here. Here's my version, if anyone's interested.

    It handles numbered placeholders like %1 as well as typed placeholders (%1s, %1d, %1) to support Integers and Floats passed in the param array.
    Speaking of the ParamArray, this routine accepts both an array of parameters or separate parameters.

    Code:
    Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
        '// Formats a string by replacing numbered placeholders (%1, %2, etc.) or typed placeholders (%1s, %1d, %1f)
        '// with corresponding arguments, supporting strings, integers, and floats
        '
        '// Accepts both a ParamArray passed in and separate args:
        '// Separate args example:
        '// result = Printf("the %1 ate the %2", "dog", "cat")
        '//
        '// Single ParamArray example:
        '// paramArrayTest = Array("dog", "cat")
        '// result = Printf("the %1 ate the %2", paramArrayTest)
    
        Dim TempPercentCharCode As Long     ' Dynamically set to an unused PUA character to temporarily replace % in arguments
        Dim argIndex As Long                ' Variable to loop through the argument array
        Dim placeholder As String           ' Variable to store the current placeholder being processed
        Dim actualArgs As Variant           ' Added to hold unwrapped arguments from a nested ParamArray
        
        Const PERCENT_CHAR As String = "%"
        
        ' Check if the input format string is empty; if so, return an empty string
        If Len(formatString) = 0 Then
            Printf = vbNullString
            Exit Function
        End If
        
        ' Find a character in the PUA range which does not appear in the formatString
        ' We're going to temporarily replace "%" in the arguments with this temp character
        TempPercentCharCode = FindUnusedPUAChar(formatString)
        
        ' Check if no arguments were provided (empty ParamArray); if so, return the format string unchanged
        If UBound(args) < LBound(args) Then
            Printf = formatString
            Exit Function
        End If
        
        ' Check if args is a nested array from another ParamArray
        If UBound(args) = LBound(args) And Not IsEmpty(args) And VarType(args(LBound(args))) = vbArray + vbVariant Then
            actualArgs = args(LBound(args)) ' Unwrap the nested array
        Else
            actualArgs = args ' Use args as-is
        End If
        
        ' Check if the unwrapped arguments are empty; if so, return the format string unchanged
        If UBound(actualArgs) < LBound(actualArgs) Then
            Printf = formatString
            Exit Function
        End If
        
        ' Loop through the arguments in reverse order (from highest index to lowest)
        For argIndex = UBound(actualArgs) To LBound(actualArgs) Step -1
            ' Calculate the base placeholder (e.g., %1, %2) for the current argument
            placeholder = PERCENT_CHAR & (argIndex - LBound(actualArgs) + 1)
            
            ' Check for typed placeholders and apply appropriate type conversion
            If InStr(formatString, placeholder & "s") > 0 Then
                ' Handle string specifier (%Ns): convert argument to string
                formatString = Replace(formatString, placeholder & "s", _
                                      Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
            ElseIf InStr(formatString, placeholder & "d") > 0 Then
                ' Handle integer specifier (%Nd): truncate (Fix) to long integer
                formatString = Replace(formatString, placeholder & "d", _
                                      Replace(CStr(Fix(actualArgs(argIndex))), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
            ElseIf InStr(formatString, placeholder & "f") > 0 Then
                ' Handle float specifier (%Nf): convert argument to double with default formatting
                formatString = Replace(formatString, placeholder & "f", _
                                      Replace(CDbl(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
            Else
                ' Default case: replace plain placeholder (e.g., %1) with argument as-is
                formatString = Replace(formatString, placeholder, _
                                      Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
            End If
        Next
        
        ' After all placeholders are replaced, restore any original % characters by
        ' replacing TempPercentCharCode back to % in the final string
        Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
    End Function
    
    ' Helper function to find an unused PUA character
    Private Function FindUnusedPUAChar(ByVal str As String) As Long
        Dim codePoint As Long
        For codePoint = &HE000& To &HF8FF& ' PUA range
            If InStr(str, ChrW$(codePoint)) = 0 Then
                FindUnusedPUAChar = codePoint
                Exit Function
            End If
        Next
        ' If no unused character is found (extremely unlikely), raise an error
        Err.Raise vbObjectError + 1002, "Printf", "No unused Private Use Area character available"
    End Function
    Last edited by AAraya; Mar 19th, 2025 at 10:46 AM.

  14. #14
    Angel of Code Niya's Avatar
    Join Date
    Nov 2011
    Posts
    8,792

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by jpbro View Post
    Good point, at best you'll get a "poor man's" string interpolation from VB6.
    Interesting you should mention this. Before it was added to .Net it was available in a poor man's version as a normal function call. The compiler implements string interpolation by simply replacing the string interpolation syntax with a call to this function.
    Treeview with NodeAdded/NodesRemoved events | BlinkLabel control | Calculate Permutations | Object Enums | ComboBox with centered items | .Net Internals article(not mine) | Wizard Control | Understanding Multi-Threading | Simple file compression | Demon Arena

    Copy/move files using Windows Shell | I'm not wanted

    C++ programmers will dismiss you as a cretinous simpleton for your inability to keep track of pointers chained 6 levels deep and Java programmers will pillory you for buying into the evils of Microsoft. Meanwhile C# programmers will get paid just a little bit more than you for writing exactly the same code and VB6 programmers will continue to whitter on about "footprints". - FunkyDexter

    There's just no reason to use garbage like InputBox. - jmcilhinney

    The threads I start are Niya and Olaf free zones. No arguing about the benefits of VB6 over .NET here please. Happiness must reign. - yereverluvinuncleber

  15. #15

    Thread Starter
    Junior Member
    Join Date
    Dec 2024
    Posts
    23

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    not a bad idea...

  16. #16

    Thread Starter
    Junior Member
    Join Date
    Dec 2024
    Posts
    23

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    hard work always return seems competitive

  17. #17

    Thread Starter
    Junior Member
    Join Date
    Dec 2024
    Posts
    23

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    Quote Originally Posted by jpbro View Post
    Here's my shot at an (optionally) recursive string formatted. Note that it's barely tested so there will be bugs, and it has not be optimized at all so it will probable perform poorly on large strings/lots of recursion.

    Code:
    Public Function RPrintF(ByVal p_String As String, ParamArray p_SubStrings() As Variant) As String
       Const c_MaxRecurse As Long = 0   ' Set to 0 to disable "pseudo-recursive" substitutions,
                                        ' or set it to a reasonable number if you want "pseudo-recursive" replacement of %# substrings (with infinite loop protection).
         
       Dim ii As Long
       Dim l_Char As String
       
       Dim l_InSubStringDef As Long
       Dim l_InSubStringIndex As Long
       Dim l_InSubStringFormatDef As Long
       
       Dim l_RecurseCount As Long
       Dim l_RecurseEnd As Long
       
       Dim l_SubStringIndex As Long
       Dim l_FormatString As String
       Dim l_FormatStringDef As String
       
       If (UBound(p_SubStrings) >= 0) And _
          (Len(p_String) > 1) Or _
          (InStr(1, p_String, "%") > 0) Then  ' Only scan for substitutions if there is string content to scan,
                                              ' substrings to substitute,
                                              ' or if there is a % character (we may need to unescape even if there are no substitution strings)
          
          ii = 1
          
          Do While ii <= Len(p_String) + 1
             If ii <= Len(p_String) Then
                l_Char = Mid$(p_String, ii, 1)
             Else
                l_Char = vbNullString
             End If
             
             Select Case l_Char
             Case "%"
                ' Check for start of substitution index marker
                
                If l_InSubStringFormatDef = 0 Then
                   If (l_InSubStringDef > 0) And (l_InSubStringDef = ii - 1) Then
                      l_InSubStringDef = 0
                      
                      p_String = Left$(p_String, ii - 1) & Mid$(p_String, ii + 1)
                      ii = ii - 1
                   Else
                      l_InSubStringDef = ii
                   End If
                End If
                
             Case "0" To "9"
                ' Check for substitution index #
                
                If l_InSubStringFormatDef = 0 Then
                   If l_InSubStringDef Then
                      l_InSubStringIndex = ii
                   Else
                      l_InSubStringIndex = 0
                   End If
                End If
                
             Case Else
                Select Case l_Char
                Case "{"
                   ' Check for start of format marker
                   
                   If l_InSubStringFormatDef = 0 Then
                      If l_InSubStringIndex > 0 Then
                         l_InSubStringFormatDef = ii
                      End If
                   ElseIf l_InSubStringFormatDef = ii - 1 Then
                      l_InSubStringFormatDef = 0
                   End If
                   
                Case "}"
                   ' Check for end of format marker
                   
                   If l_InSubStringFormatDef > 0 Then
                      If Mid$(p_String, ii + 1, 1) <> "}" Then
                         ' Found end of format string
                         l_FormatString = Mid$(p_String, l_InSubStringFormatDef + 1, ii - l_InSubStringFormatDef - 1)
                         l_FormatStringDef = "{" & l_FormatString & "}"
                         l_InSubStringFormatDef = 0
                      End If
                   End If
                End Select
                
                If (l_InSubStringIndex > 0) And _
                   (l_InSubStringDef > 0) And _
                   (l_InSubStringFormatDef = 0) Then
                   
                   ' Perform substitution
                   
                   l_SubStringIndex = Mid$(p_String, l_InSubStringDef + 1, l_InSubStringIndex - l_InSubStringDef)
                   
                   If l_SubStringIndex > 0 And l_SubStringIndex <= UBound(p_SubStrings) + 1 Then
                      l_RecurseEnd = l_InSubStringDef + Len(Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString))
                      l_RecurseCount = l_RecurseCount + 1
                                     
                      p_String = Mid$(p_String, 1, l_InSubStringDef - 1) & Replace$(p_String, "%" & l_SubStringIndex & l_FormatStringDef, Format$(p_SubStrings(l_SubStringIndex - 1), l_FormatString), l_InSubStringDef, 1)
                   
                      ii = l_InSubStringDef - 1
                   End If
                   
                   l_FormatString = vbNullString
                   l_FormatStringDef = vbNullString
                   
                   l_InSubStringIndex = 0
                   l_InSubStringDef = 0
                   l_InSubStringFormatDef = 0
                
                ElseIf l_InSubStringFormatDef = 0 Then
                   l_InSubStringDef = 0
                
                End If
             End Select
                      
             ii = ii + 1
             
             ' Infinite loop protection
             If l_RecurseEnd > 0 Then If ii > l_RecurseEnd Then l_RecurseCount = 0: l_RecurseEnd = 0
             If l_RecurseCount > c_MaxRecurse Then ii = l_RecurseEnd + 1
          Loop
       End If
       
       RPrintF = p_String
    End Function

    Some notes:

    • Substitution indexes are identified by a leading % character, and are 1-based. For example, RPrintF("Print %1", "Test") will output Print Test.

    • You can add a format string after any substitution identifier by enclosing it in {braces}. Format strings can be anything that the VB6 Format function accepts. For example, RPrintF("Today is %1{Short Date}", Now) will return Today is 2025/03/17 (or whatever your system's short date format is)

    • Substitution indexes do not have to be in order, so RPrintF("%2 %1", "B", "A") will work and return A B

    • Recursion is supported with infinite loop protection if you set the c_MaxRecurse constant > 0. If you enable recursion, then something like this: RPrintF("Let's do %1.", "something %3", "terrible", "exciting") will return Let's do something exciting.

    • If you mess up and try to do something like RPrintF("This is an infinite loop bomb %1", "Infinite Loop Bomb %2", "Infinite Loop Bomb %1") with recursion enabled you will get a crazy result, but you won't lock the program.

    • You can escape special characters by doubling them up. For example %% will escape % so it doesn't get misinterpreted as the start of a sub-string identifier.


    Hope it comes in handy, but please remember that the code has barely been tested so please report any problems here (or feel free to fix them and post the results here).
    thank your for you valuable time and effort

  18. #18
    Fanatic Member
    Join Date
    Aug 2011
    Location
    Palm Coast, FL
    Posts
    674

    Re: String Interpolation, String Formatting, Composite Formatting, Template Literals

    I like the support for custom format strings in jpbro's code. I've added that in my version. I'm also adding some unit tests which I created to test the new function. Some advantages of my approach are:

    1. Can accept both separate arguments and a single ParamArray
    2. Supports typed placeholders like %1s for strings, %1d for integers (with truncation), and %1f for floats
    3. Temporarily replaces % in arguments with a dynamically chosen Private Use Area (PUA) character, preventing interference with placeholder detection
    4. Reverse Order Processing: Mine processes arguments from the highest to the lowest index, avoiding issues with overlapping placeholders and ensuring reliable substitutions.
    5. No Recursion: Mine processes placeholders in a single pass

    If your use case involves complex, nested formatting where placeholders depend on other placeholders, then jbpro's RPrintf recursion would absolutely be an advantage, and would serve you better. But if your needs are simpler, my Printf’s lack of recursion is safer and faster.

    Code:
    Public Function Printf(ByVal formatString As String, ParamArray args() As Variant) As String
        '// Formats a string by replacing numbered placeholders (%1, %2, etc.), typed placeholders (%1s, %1d, %1f),
        '// or formatted placeholders (%1{format}) with corresponding arguments.
        '// Supports %% for literal % and custom formatting via VB6 Format$.
        '//
        '// Accepts both a ParamArray passed in and separate args:
        '// Separate args example:
        '// result = Printf("the %1 ate the %2", "dog", "cat")
        '//
        '// Single ParamArray example:
        '// paramArrayTest = Array("dog", "cat")
        '// result = Printf("the %1 ate the %2", paramArrayTest)
    
        Dim TempPercentCharCode As Long     ' Dynamically set to an unused PUA character to temporarily replace % in arguments
        Dim argIndex As Long                ' Variable to loop through the argument array
        Dim placeholder As String           ' Variable to store the current placeholder being processed
        Dim actualArgs As Variant           ' Added to hold unwrapped arguments from a nested ParamArray
        Dim formatStart As Long             ' Position of '{' in %N{format}
        Dim formatEnd As Long               ' Position of '}' in %N{format}
        Dim formatSpec As String            ' Extracted format specifier from {format}
        
        Const PERCENT_CHAR As String = "%"
        
        ' Check if the input format string is empty; if so, return an empty string
        If Len(formatString) = 0 Then
            Printf = vbNullString
            Exit Function
        End If
        
        ' Find a character in the PUA range which does not appear in the formatString
        ' We’ll temporarily replace % in arguments and escaped %% with this character
        TempPercentCharCode = FindUnusedPUAChar(formatString)
        
        ' Preprocess %% to TempPercentCharCode to handle escaping
        formatString = Replace(formatString, "%%", ChrW$(TempPercentCharCode))
        
        ' Check if no arguments were provided (empty ParamArray); if so, return the format string with TempPercentCharCode restored
        If UBound(args) < LBound(args) Then
            Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
            Exit Function
        End If
        
        ' Check if args is a nested array from another ParamArray
        If UBound(args) = LBound(args) And Not IsEmpty(args) And VarType(args(LBound(args))) = vbArray + vbVariant Then
            actualArgs = args(LBound(args)) ' Unwrap the nested array
        Else
            actualArgs = args ' Use args as-is
        End If
        
        ' Check if the unwrapped arguments are empty; if so, return the format string with TempPercentCharCode restored
        If UBound(actualArgs) < LBound(actualArgs) Then
            Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
            Exit Function
        End If
        
        ' Loop through the arguments in reverse order (from highest index to lowest)
        For argIndex = UBound(actualArgs) To LBound(actualArgs) Step -1
            ' Calculate the base placeholder (e.g., %1, %2) for the current argument
            placeholder = PERCENT_CHAR & (argIndex - LBound(actualArgs) + 1)
            
            ' Check for formatted placeholder (e.g., %1{0.00})
            formatStart = InStr(formatString, placeholder & "{")
            If formatStart > 0 Then
                formatEnd = InStr(formatStart + 1, formatString, "}")
                If formatEnd > 0 Then
                    formatSpec = Mid$(formatString, formatStart + Len(placeholder) + 1, formatEnd - formatStart - Len(placeholder) - 1)
                    ' Apply Format$ with the specified format
                    formatString = Replace(formatString, placeholder & "{" & formatSpec & "}", _
                                          Replace(Format$(actualArgs(argIndex), formatSpec), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
                End If
            Else
                ' Check for typed placeholders and apply appropriate type conversion
                If InStr(formatString, placeholder & "s") > 0 Then
                    ' Handle string specifier (%Ns): convert argument to string
                    formatString = Replace(formatString, placeholder & "s", _
                                          Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
                ElseIf InStr(formatString, placeholder & "d") > 0 Then
                    ' Handle integer specifier (%Nd): truncate (Fix) to long integer
                    formatString = Replace(formatString, placeholder & "d", _
                                          Replace(CStr(Fix(actualArgs(argIndex))), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
                ElseIf InStr(formatString, placeholder & "f") > 0 Then
                    ' Handle float specifier (%Nf): convert argument to double with default formatting
                    formatString = Replace(formatString, placeholder & "f", _
                                          Replace(CDbl(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
                Else
                    ' Default case: replace plain placeholder (e.g., %1) with argument as-is
                    formatString = Replace(formatString, placeholder, _
                                          Replace(CStr(actualArgs(argIndex)), PERCENT_CHAR, ChrW$(TempPercentCharCode)))
                End If
            End If
        Next
        
        ' After all placeholders are replaced, restore any original % characters by
        ' replacing TempPercentCharCode back to % in the final string
        Printf = Replace(formatString, ChrW$(TempPercentCharCode), PERCENT_CHAR)
    End Function
    
    ' Helper function to find an unused PUA character
    Private Function FindUnusedPUAChar(ByVal str As String) As Long
        Dim codePoint As Long
        For codePoint = &HE000& To &HF8FF& ' PUA range
            If InStr(str, ChrW$(codePoint)) = 0 Then
                FindUnusedPUAChar = codePoint
                Exit Function
            End If
        Next
        ' If no unused character is found (extremely unlikely), raise an error
        Err.Raise vbObjectError + 1002, "Printf", "No unused Private Use Area character available"
    End Function
    
    Public Sub TestPrintfScenarios()
        Dim result As String
        Dim paramArrayTest As Variant
        
        ' Test 1: Separate Arguments with Plain Placeholders
        Debug.Print "Test 1: Separate arguments with plain placeholders"
        result = Printf("the %1 ate the %2", "dog", "cat")
        Debug.Print "Result: " & result
        Debug.Print "Expected: the dog ate the cat"
        Debug.Print "Pass: " & (result = "the dog ate the cat")
        Debug.Print "----------------------------------------"
        
        ' Test 2: Single ParamArray with Plain Placeholders
        Debug.Print "Test 2: Single ParamArray with plain placeholders"
        paramArrayTest = Array("dog", "cat")
        result = Printf("the %1 ate the %2", paramArrayTest)
        Debug.Print "Result: " & result
        Debug.Print "Expected: the dog ate the cat"
        Debug.Print "Pass: " & (result = "the dog ate the cat")
        Debug.Print "----------------------------------------"
        
        ' Test 3: Type Specifiers (string, integer, float)
        Debug.Print "Test 3: Type specifiers (%1s, %2d, %3f)"
        result = Printf("Name: %1s, Age: %2d, Height: %3f", "Alice", 25.7, 5.9)
        Debug.Print "Result: " & result
        Debug.Print "Expected: Name: Alice, Age: 25, Height: 5.9"
        Debug.Print "Pass: " & (result = "Name: Alice, Age: 25, Height: 5.9")
        Debug.Print "----------------------------------------"
        
        ' Test 4: Empty Format String
        Debug.Print "Test 4: Empty format string"
        result = Printf("", "dog", "cat")
        Debug.Print "Result: " & result
        Debug.Print "Expected: (empty string)"
        Debug.Print "Pass: " & (result = vbNullString)
        Debug.Print "----------------------------------------"
        
        ' Test 5: No Arguments
        Debug.Print "Test 5: No arguments"
        result = Printf("the %1 ate the %2")
        Debug.Print "Result: " & result
        Debug.Print "Expected: the %1 ate the %2"
        Debug.Print "Pass: " & (result = "the %1 ate the %2")
        Debug.Print "----------------------------------------"
        
        ' Test 6: Mixed Types and Invalid Conversion
        Debug.Print "Test 6: Mixed types with potential invalid conversion"
        On Error Resume Next
        result = Printf("Value: %1d, Text: %2s", "not_a_number", 42)
        If Err.Number <> 0 Then
            result = "Error: " & Err.Description
        End If
        On Error GoTo 0
        Debug.Print "Result: " & result
        Debug.Print "Expected: (error due to invalid integer conversion)"
        Debug.Print "Pass: " & (InStr(result, "Error") > 0)
        Debug.Print "----------------------------------------"
        
        ' Test 7: Preservation of % in Arguments
        Debug.Print "Test 7: Preservation of % in arguments"
        result = Printf("Discount: %1, Rate: %2", "50%", "25%")
        Debug.Print "Result: " & result
        Debug.Print "Expected: Discount: 50%, Rate: 25%"
        Debug.Print "Pass: " & (result = "Discount: 50%, Rate: 25%")
        Debug.Print "----------------------------------------"
        
        ' Test 8: Format String with Original TEMP_PERCENT (&HE1B6&)
        Debug.Print "Test 8: Format string containing original TEMP_PERCENT"
        result = Printf("Test" & ChrW$(&HE1B6&) & " %1", "value")
        Debug.Print "Result: " & result
        Debug.Print "Expected: Test" & ChrW$(&HE1B6&) & " value"
        Debug.Print "Pass: " & (result = "Test" & ChrW$(&HE1B6&) & " value")
        Debug.Print "----------------------------------------"
        
        ' Test 9: Custom Numeric Format Specifier
        Debug.Print "Test 9: Custom Numeric Format Specifier"
        result = Printf("Price: %1{0.00}, Count: %2{000}", 5.9, 7)
        Debug.Print "Result: " & result
        Debug.Print "Expected: Price: 5.90, Count: 007"
        Debug.Print "Pass: " & (result = "Price: 5.90, Count: 007")
        Debug.Print "----------------------------------------"
        
        ' Test 10: Custom Date Format Specifier
        Debug.Print "Test 10: Custom Date Format Specifier"
        result = Printf("Date: %1{mmmm d, yyyy}", #1/1/2023#)
        Debug.Print "Result: " & result
        Debug.Print "Expected: Date: January 1, 2023"
        Debug.Print "Pass: " & (result = "Date: January 1, 2023")
        Debug.Print "----------------------------------------"
        
        ' Test 11: Escaped %%
        Debug.Print "Test 11: Escaped %%"
        result = Printf("Literal %% with %1", "test")
        Debug.Print "Result: " & result
        Debug.Print "Expected: Literal % with test"
        Debug.Print "Pass: " & (result = "Literal % with test")
        Debug.Print "----------------------------------------"
        
    End Sub
    Last edited by AAraya; Mar 22nd, 2025 at 09:43 AM.

Tags for this Thread

Posting Permissions

  • You may not post new threads
  • You may not post replies
  • You may not post attachments
  • You may not edit your posts
  •  



Click Here to Expand Forum to Full Width