Author Topic: Format$ function with indexed & reusable arguments  (Read 9950 times)

0 Members and 1 Guest are viewing this topic.

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Format$ function with indexed & reusable arguments
« on: August 18, 2020, 05:41:50 pm »
As the title says,
this is a Format$ function (PRINT USING style), which allows for explicit argument indexing in the given format template. The indexing feature lets you change the template being processed while keeping the arguments stream the same.

This can be an invaluable tool in some situations, just think about different date formats. With PRINT USING alone you would probably need to reorder your day, month, year, weekday etc. arguments for every date format to match the order of the respective format tokens in the template, but not so with this function. Another example is when it comes to localizing your programs, where it helps when translating template strings to different languages and the sentence structure and thus the order of the arguments changes.

Beside the indexing, which this function got its name from, it has a couple other useful extensions compared to the regular PRINT USING conventions, such as bin/hex/oct formatting, use of escape sequences to insert control or extended ASCII chars and more. All of this is detailed in the comprehensive HTML Documentation available for download below the example code block.

An example using the new IndexFormat function:
Save as: FormatExample.bas (or whatever)
Code: QB64: [Select]
  1. _TITLE "FormatExample"
  2. '=== Full description for the IndexFormat$() function is available
  3. '=== in the separate HTML document.
  4. '=====================================================================
  5. WIDTH , 30
  6.  
  7.  
  8. '-- The following format templates need its arguments in different order,
  9. '-- no problem with indexing, no need to reorder the given arguments.
  10. '-- You may even use different formatting for the same argument, as long
  11. '-- as its types are compatible (ie. string vs. number).
  12. dateDE$ = "Date format Germany: 0{#}. 1{&} 2{####}" ' 1{} = full string
  13. dateUS$ = "Date format US     : 1{\ \}/0{#} 2{####}" '1{} = first 3 chars only
  14.  
  15. '-- The easiest way to pass a variable number of arguments, which may
  16. '-- even be of different types, to a user function is using a string.
  17. '-- All arguments will be concatenated in this string, separated by a
  18. '-- designated char/string which does not appear in the arguments itself.
  19. '-- Strings can be added as is, numbers can be added as literal strings
  20. '-- too, or in the form STR$(variable).
  21. year% = 2021
  22. argStr$ = "2|Januar|" + STR$(year%)
  23. '-- In this example the | is the argument separator. Use whatever is
  24. '-- suitable for your needs, maybe even a CHR$(0).
  25.  
  26. '-- Now let's test the whole thing, we've different token orders in the
  27. '-- format templates, but use the same argument string for both calls.
  28. PRINT IndexFormat$(dateDE$, argStr$, "|")
  29. PRINT IndexFormat$(dateUS$, argStr$, "|")
  30.  
  31.  
  32.  
  33. '-- And here the examples from the function description, which also
  34. '-- shows the reuse of arguments without the need to pass more arguments
  35. '-- for the additional "feet" and "toes" format tokens.
  36. head = 1: hands = 2: fingers = 10
  37. PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
  38. PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
  39.  
  40. argStr$ = STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers)
  41. PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands", argStr$, "|")
  42. PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, also 1{##} feet and 2{##} toes", argStr$, "|")
  43.  
  44.  
  45.  
  46. '-- The function can also handle escape sequences as known from C/C++,
  47. '-- so you may use those sequences within your format templates.
  48. PRINT IndexFormat$("Column-1\tColumn-2\tColumn-3\n0{#.##}\t\t1{#.##}\t\t2{#.##}", "1.11|2.22|3.33", "|")
  49. PRINT IndexFormat$("This is a \x220{&}\x22 section.", "quoted", "|")
  50. '-- Using escape sequences and the new bin/dec/hex/oct/real formatting,
  51. '-- while reusing the same argument for all tokens. Also showing the use
  52. '-- of preferences specifiers to group bin and hex outputs.
  53. PRINT IndexFormat$(" Bin: 0{?4:B16}\n Dec: 0{D}\n Hex: 0{?2:H8}\n Oct: 0{O}\nReal: 0{R}\n", "2021.00548", "|")
  54.  
  55.  
  56.  
  57. '-- Alignment of strings in a fixed length field, the square brackets are
  58. '-- just used to better visualize the field.
  59. PRINT IndexFormat$("[0{?L:\             \}]", "RhoSigma", "|")
  60. PRINT IndexFormat$("[0{?C:\             \}]", "RhoSigma", "|")
  61. PRINT IndexFormat$("[0{?R:\             \}]", "RhoSigma", "|")
  62.  
  63.  
  64.  
  65. '-- Finally a currency example with replaced dollar sign and flipped
  66. '-- comma/dot notation. I'd like to get that much for this function ;)
  67. PRINT IndexFormat$("Account balance: 0{?î,:**$#####,.##}", "12345.67", "|")
  68.  
  69.  
  70. '-- done
  71.  
  72.  
  73.  
  74.  
  75.  
  76. '--- Full description available in separate HTML document.
  77. '---------------------------------------------------------------------
  78. FUNCTION IndexFormat$ (fmt$, arg$, sep$)
  79. '--- option _explicit requirements ---
  80. DIM args$, shan&, dhan&, than&, idx%, cpos&, res$, lit%, tok%, ft$, cch$
  81. DIM och$, opos&, tmp$, fp$, tyl%, typ$, oval&&, temp~&&, curr%, high%
  82. '--- init ---
  83. args$ = arg$ 'avoid side effects
  84. shan& = _SOURCE: dhan& = _DEST: than& = _NEWIMAGE(256, 1, 0)
  85. _SOURCE than&: _DEST than&
  86. REDIM argArr$(0 TO 35) 'all args empty
  87. '--- parse arguments ---
  88. IF RIGHT$(args$, LEN(sep$)) <> sep$ THEN args$ = args$ + sep$
  89. FOR idx% = 0 TO 35
  90.     cpos& = INSTR(args$, sep$): IF cpos& = 0 THEN EXIT FOR
  91.     argArr$(idx%) = LEFT$(args$, cpos& - 1)
  92.     args$ = MID$(args$, cpos& + LEN(sep$))
  93. NEXT idx%
  94. '--- process format template ---
  95. res$ = "": lit% = 0: tok% = 0: ft$ = "": idx% = -1
  96. FOR cpos& = 1 TO LEN(fmt$)
  97.     cch$ = MID$(fmt$, cpos&, 1)
  98.     IF cch$ = "_" AND lit% = 0 THEN 'take next \{} as literal
  99.         IF NOT tok% THEN lit% = -1
  100.     ELSEIF cch$ = "\" AND lit% = 0 AND tok% = 0 THEN 'insert esc sequence
  101.         IF cpos& < LEN(fmt$) THEN
  102.             SELECT CASE UCASE$(MID$(fmt$, cpos& + 1, 1))
  103.                 CASE "A": och$ = CHR$(7) ' audio bell
  104.                 CASE "B": och$ = CHR$(8) ' backspace
  105.                 CASE "T": och$ = CHR$(9) ' tabulator
  106.                 CASE "N": och$ = CHR$(10) 'line feed
  107.                 CASE "V": och$ = CHR$(11) 'vertical tabulator
  108.                 CASE "F": och$ = CHR$(12) 'form feed
  109.                 CASE "R": och$ = CHR$(13) 'carriage return
  110.                 CASE "E": och$ = CHR$(27) 'escape
  111.                 CASE "0", "1", "2", "3" '  octal ASCII (3 digits)
  112.                     och$ = CHR$(VAL("&O" + MID$(fmt$, cpos& + 1, 3)))
  113.                     cpos& = cpos& + 2
  114.                 CASE "X" '                 hex ASCII (x + 2 digits)
  115.                     och$ = CHR$(VAL("&H" + MID$(fmt$, cpos& + 2, 2)))
  116.                     cpos& = cpos& + 2
  117.                 CASE ELSE: och$ = "" '     ignore unknowns
  118.             END SELECT
  119.             res$ = res$ + och$
  120.             cpos& = cpos& + 1: opos& = cpos&
  121.         END IF
  122.     ELSEIF cch$ = "{" AND lit% = 0 THEN 'begin of formatting token
  123.         IF idx% = -1 THEN
  124.             och$ = UCASE$(MID$(fmt$, cpos& - 1, 1)): tok% = -1
  125.             IF ((cpos& - 1) = opos&) OR ((och$ < "0" OR och$ > "9") AND (och$ < "A" OR och$ > "Z")) THEN och$ = "-"
  126.             IF och$ = "-" THEN och$ = "0": ELSE res$ = LEFT$(res$, LEN(res$) - 1)
  127.             IF och$ >= "A" THEN idx% = ASC(och$) - 55: ELSE idx% = VAL(och$)
  128.         END IF
  129.     ELSEIF cch$ = "}" AND lit% = 0 THEN 'end of formatting token
  130.         IF idx% >= 0 THEN
  131.             GOSUB doArgFormat: res$ = res$ + tmp$
  132.             tok% = 0: ft$ = "": idx% = -1
  133.         END IF
  134.     ELSE 'accumulate chars/symbols in correct channel
  135.         IF lit% AND INSTR("\{}", cch$) = 0 THEN cch$ = "_" + cch$
  136.         IF tok% THEN ft$ = ft$ + cch$: ELSE res$ = res$ + cch$
  137.         lit% = 0
  138.     END IF
  139. NEXT cpos&
  140. '--- cleanup & set result ---
  141. ERASE argArr$
  142. _SOURCE shan&: _DEST dhan&: _FREEIMAGE than&
  143. IndexFormat$ = res$
  144. '-----------------------------
  145. doArgFormat:
  146. CLS: tmp$ = "": fp$ = "": ft$ = LTRIM$(RTRIM$(ft$))
  147. IF LEFT$(ft$, 1) = "?" THEN
  148.     tyl% = INSTR(2, ft$, ":")
  149.     IF tyl% > 0 THEN fp$ = LEFT$(MID$(ft$, 2, tyl% - 2), 2): ft$ = LTRIM$(MID$(ft$, tyl% + 1)) 'extract format prefs
  150. IF ft$ = "" THEN RETURN 'empty token = empty formatted
  151.     CASE "!", "&", "\" 'regular string formatting
  152.         IF LEFT$(ft$, 1) = "\" THEN
  153.             tyl% = INSTR(2, ft$, "\"): IF tyl% = 0 THEN ft$ = "\" + ft$: tyl% = 2
  154.             IF LTRIM$(fp$) <> "" AND LEN(argArr$(idx%)) < tyl% THEN
  155.                 SELECT CASE LEFT$(LTRIM$(fp$), 1)
  156.                     CASE "C", "c": tyl% = (tyl% - LEN(argArr$(idx%))) \ 2
  157.                     CASE "R", "r": tyl% = tyl% - LEN(argArr$(idx%))
  158.                     CASE ELSE: tyl% = 0 'L or Unknown is default (left)
  159.                 END SELECT
  160.                 argArr$(idx%) = SPACE$(tyl%) + argArr$(idx%)
  161.             END IF
  162.         END IF
  163.         PRINT USING ft$; argArr$(idx%);: fp$ = ""
  164.     CASE "B", "D", "H", "O", "R" 'extended number formatting (bin/dec/hex/oct/real)
  165.         typ$ = LEFT$(ft$, 1): tyl% = VAL(MID$(ft$, 2))
  166.         SELECT CASE typ$
  167.             CASE "B", "b": GOSUB doBinString
  168.             CASE "D", "d": tmp$ = LTRIM$(STR$(_ROUND(VAL(argArr$(idx%)))))
  169.             CASE "H", "h"
  170.                 tmp$ = HEX$(VAL(argArr$(idx%)))
  171.                 IF typ$ = "H" THEN tmp$ = UCASE$(tmp$): ELSE tmp$ = LCASE$(tmp$)
  172.             CASE "O", "o": tmp$ = OCT$(VAL(argArr$(idx%)))
  173.             CASE "R", "r": tmp$ = LTRIM$(STR$(VAL(argArr$(idx%)))): fp$ = ""
  174.         END SELECT
  175.         IF tyl% > 0 THEN 'adjust field length (if any)
  176.             IF LEN(tmp$) <= tyl% THEN
  177.                 tmp$ = RIGHT$(STRING$(tyl%, "0") + tmp$, tyl%): idx% = INSTR(tmp$, "-")
  178.                 IF idx% > 0 THEN
  179.                     typ$ = UCASE$(MID$(tmp$, idx% - 1, 1))
  180.                     IF typ$ <> "E" AND typ$ <> "D" THEN tmp$ = "-" + LEFT$(tmp$, idx% - 1) + MID$(tmp$, idx% + 1)
  181.                 END IF
  182.             ELSE
  183.                 tmp$ = "%" + tmp$
  184.             END IF
  185.         END IF
  186.         IF LTRIM$(fp$) <> "" THEN 'apply grouping (if any)
  187.             typ$ = "": tyl% = 0
  188.             FOR idx% = LEN(tmp$) TO 1 STEP -1
  189.                 typ$ = MID$(tmp$, idx%, 1) + typ$: tyl% = tyl% + 1
  190.                 IF tyl% = VAL(fp$) THEN typ$ = " " + typ$: tyl% = 0
  191.             NEXT idx%
  192.             tmp$ = LTRIM$(typ$): IF LEFT$(tmp$, 2) = "- " THEN tmp$ = "-" + MID$(tmp$, 3)
  193.         END IF
  194.         RETURN
  195.     CASE ELSE 'regular number formatting (or invalid nonsense)
  196.         IF INSTR(ft$, "**") = 0 AND INSTR(ft$, "$$") = 0 AND INSTR(ft$, "#") = 0 THEN
  197.             PRINT ft$; 'take nonsense as is
  198.         ELSE
  199.             PRINT USING ft$; VAL(argArr$(idx%));
  200.         END IF
  201. tyl% = INSTR(fp$, ","): IF tyl% > 0 THEN MID$(fp$, tyl%, 1) = " "
  202. fp$ = LTRIM$(RTRIM$(fp$))
  203. FOR idx% = 1 TO POS(0) - 1
  204.     typ$ = CHR$(SCREEN(1, idx%)): ft$ = typ$
  205.     IF fp$ <> "" AND typ$ = "$" THEN ft$ = fp$
  206.     IF tyl% > 0 AND typ$ = "," THEN ft$ = "."
  207.     IF tyl% > 0 AND typ$ = "." THEN ft$ = ","
  208.     tmp$ = tmp$ + ft$
  209. NEXT idx%
  210. '-----------------------------
  211. doBinString:
  212. oval&& = VAL(argArr$(idx%)): temp~&& = oval&&
  213. tmp$ = STRING$(64, "0"): curr% = 64: high% = 64
  214.     IF (temp~&& AND 1) THEN ASC(tmp$, curr%) = 49: high% = curr%
  215.     curr% = curr% - 1: temp~&& = temp~&& \ 2
  216. LOOP UNTIL temp~&& = 0
  217. IF oval&& < 0 THEN
  218.     IF -oval&& < &H0080000000~&& THEN high% = 33
  219.     IF -oval&& < &H0000008000~&& THEN high% = 49
  220.     IF -oval&& < &H0000000080~&& THEN high% = 57
  221. tmp$ = MID$(tmp$, high%)
  222.  
  223.  

As it is required to preserve the UTF-8 encoding of the HTML Documentation and some preformatted examples, it is packed into an 7-zip archive file attached below. The archive does also contain the example from the codebox above.
* IndexFormat.7z (Filesize: 8.66 KB, Downloads: 147)
« Last Edit: October 13, 2021, 07:52:21 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #1 on: August 18, 2020, 09:12:01 pm »
Code: QB64: [Select]
  1. _TITLE "Thankyou Note" 'b+ 2020-08-18
  2. delimiter$ = "|" ' comma probably not a good idea
  3. args$ = "Tuesday|August|18|2020|RhoSigma|IndexFormat$ Function|It is interesting and should prove handy."
  4. ThankyouNote$ = CHR$(10) + CHR$(10) + SPACE$(40) + "0{&} 1{&} 2{&}, 3{&}"_
  5. + CHR$(10) + CHR$(10)+ CHR$(10) + CHR$(10) + CHR$(10) + CHR$(9) + "Dear 4{&}," + CHR$(10) + chr$(10) + CHR$(9) + "Thankyou for the 5{&}."+ CHR$(10) + CHR$(10) +" "_
  6. + chr$(9) + "6{&}" + chr$(10) + chr$(10) + chr$(10) + chr$(9) + "Sincerely, bplus"
  7. PRINT IndexFormat$(ThankyouNote$, args$, delimiter$)
  8.  
  9. '-------------------
  10. '--- IndexFormat ---
  11. '-------------------
  12. ' This function will perform PRINT USING like formatting and return the
  13. ' formatted output string, so you can either store it in a variable or
  14. ' print it directly. An extension to the standard PRINT USING conventions
  15. ' used by IndexFormat() is argument position specification. Specifying
  16. ' the argument position lets the order of the various format tokens change
  17. ' while the arguments provided can remain the same.
  18. '
  19. ' Have a look on the following examples:
  20. '   Assuming head = 1, hands = 2 and fingers = 10,
  21. '     PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
  22. '     PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
  23. '     PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands",_
  24. '               STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  25. '  These three statements would produce the following output:
  26. '     " 1 head,  2 hands and 10 fingers" (args order matches format)
  27. '     " 1 fingers,  2 head and 10 hands" (args order wrong for format)
  28. '     "10 fingers,  1 head and  2 hands" (indexed, format picks right args)
  29. '
  30. ' Even better, you may reuse any arguments without providing them
  31. ' several times:
  32. '     PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, " +_
  33. '               "also 1{##} feet and 2{##} toes",_
  34. '               STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  35. '  This would produce the following output:
  36. '     " 1 head,  2 hands and 10 fingers, also  2 feet and 10 toes"
  37. '
  38. ' The argument positioning feature lets you change the format string
  39. ' being processed while keeping the arguments stream the same. This is an
  40. ' invaluable tool especially when localising your programs, as it helps
  41. ' a lot when translating strings to different languages and the sentence
  42. ' structure and thus the order of the arguments changes.
  43. '----------
  44. ' SYNTAX:
  45. '   formatted$ = IndexFormat$ (fmt$, arg$, sep$)
  46. '----------
  47. ' INPUTS:
  48. '   --- fmt$ ---
  49. '    The format string as usual for PRINT USING, but all format symbols
  50. '    enclosed in curly backets with a leading one digit index number,
  51. '    which designates the argument to use (ie. 0{..} to 9{..}). All
  52. '    argument indices may be reused as often as you want. Missing args
  53. '    will be processed as empty or zero, no error will occur.
  54. '    Write _{ and _} to get literal curly brackets in the output and
  55. '    __ to get an underscore. No underscores allowed inside {} tokens.
  56. '   --- arg$ ---
  57. '    This is a concatenated string of all arguments separated by a user
  58. '    chosen char. Use STR$(numVar) to add in a numeric variable. It won't
  59. '    hurt, if there are more arguments than used in the format string.
  60. '   --- sep$ ---
  61. '    The char used to separate the arguments in the concatenated argument
  62. '    string. Choose a char, which is not used in the arguments itself.
  63. '    If you need all printable chars, then CHR$(0) might be suitable.
  64. '----------
  65. ' RESULT:
  66. '   --- formatted$ ---
  67. '    Regulary the formatted string on success. If there's a problem with
  68. '    your provided format string, then a appropriate error message will
  69. '    be returend by default. If you don't like that and rather want the
  70. '    function to return an empty string on failure, then simply adjust
  71. '    the "CONST errMsg" line right below to switch the error behavior.
  72. '---------------------------------------------------------------------
  73. FUNCTION IndexFormat$ (fmt$, arg$, sep$)
  74.     '--- set your desired error behavior ---
  75.     CONST errMsg = "ON" 'ON = return error messages, OFF = return empty
  76.     '--- option _explicit requirements ---
  77.     DIM args$, shan&, dhan&, than&, i%, spos&, res$
  78.     DIM idx%, ft$, tok%, lit%, cpos&, cch$, pch$, tmp$
  79.     '--- init ---
  80.     args$ = arg$ 'avoid side effects
  81.     shan& = _SOURCE: dhan& = _DEST: than& = _NEWIMAGE(256, 1, 0)
  82.     _SOURCE than&: _DEST than&
  83.     REDIM argArr$(0 TO 9) 'all empty
  84.     '--- parse arguments ---
  85.     IF RIGHT$(args$, 1) <> sep$ THEN args$ = args$ + sep$
  86.     FOR i% = 0 TO 9
  87.         spos& = INSTR(args$, sep$)
  88.         IF spos& = 0 THEN EXIT FOR
  89.         argArr$(i%) = LEFT$(args$, spos& - 1)
  90.         args$ = MID$(args$, spos& + 1)
  91.     NEXT i%
  92.     '--- process format string ---
  93.     res$ = "": idx% = -1: ft$ = "": tok% = 0: lit% = 0
  94.     FOR cpos& = 1 TO LEN(fmt$)
  95.         cch$ = MID$(fmt$, cpos&, 1)
  96.         IF cch$ = "_" AND lit% = 0 THEN
  97.             IF NOT tok% THEN
  98.                 lit% = -1
  99.             ELSE
  100.                 res$ = "ERROR: No _ allowed inside {} in fmt$ at position" + STR$(cpos&) + " !!"
  101.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  102.                 EXIT FOR
  103.             END IF
  104.         ELSEIF cch$ = "{" AND lit% = 0 THEN
  105.             pch$ = MID$(fmt$, cpos& - 1, 1)
  106.             IF pch$ >= "0" AND pch$ <= "9" THEN
  107.                 IF idx% = -1 THEN
  108.                     res$ = LEFT$(res$, LEN(res$) - 1)
  109.                     idx% = VAL(pch$): tok% = -1
  110.                 ELSE
  111.                     res$ = "ERROR: Unexpected { in fmt$ at position" + STR$(cpos&) + " !!"
  112.                     IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  113.                     EXIT FOR
  114.                 END IF
  115.             ELSE
  116.                 res$ = "ERROR: Missing index before { in fmt$ at position" + STR$(cpos&) + " !!"
  117.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  118.                 EXIT FOR
  119.             END IF
  120.         ELSEIF cch$ = "}" AND lit% = 0 THEN
  121.             IF idx% >= 0 THEN
  122.                 GOSUB doArgFormat: res$ = res$ + tmp$
  123.                 idx% = -1: ft$ = "": tok% = 0
  124.             ELSE
  125.                 res$ = "ERROR: Unexpected } in fmt$ at position" + STR$(cpos&) + " !!"
  126.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  127.                 EXIT FOR
  128.             END IF
  129.         ELSE
  130.             IF tok% THEN ft$ = ft$ + cch$: ELSE res$ = res$ + cch$
  131.             lit% = 0
  132.         END IF
  133.     NEXT cpos&
  134.     '--- cleanup & set result ---
  135.     ERASE argArr$
  136.     _SOURCE shan&: _DEST dhan&: _FREEIMAGE than&
  137.     IndexFormat$ = res$
  138.     '-----------------------------
  139.     doArgFormat:
  140.     CLS: tmp$ = ""
  141.     IF ft$ = "" THEN RETURN
  142.     SELECT CASE LEFT$(ft$, 1)
  143.         CASE "!", "&", "\": PRINT USING ft$; argArr$(idx%);
  144.         CASE ELSE: PRINT USING ft$; VAL(argArr$(idx%));
  145.     END SELECT
  146.     FOR i% = 1 TO POS(0) - 1
  147.         tmp$ = tmp$ + CHR$(SCREEN(1, i%))
  148.     NEXT i%
  149.     RETURN
  150.  

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Format$ function with indexed & reusable arguments
« Reply #2 on: August 19, 2020, 01:18:47 am »
Thanx for your beautiful Note :), glad you like it, mayby this function is another piece for the Samples/Informatics section?

BTW - Your message shows me, that it might be a good idea too, to implement the standard escape sequences \n, \r, \t, \" etc. to avoid the bulky need for + CHR$(10) + an so. Just give me a day or two.
« Last Edit: August 19, 2020, 01:54:01 am by RhoSigma »
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #3 on: August 19, 2020, 02:07:21 am »
Quote
this function is another piece for the Samples/Informatics section?

I think I have to agree, a little bit of a learning curve but it fills a hole missing from QB 4.5. I think it was QB 4.5 that had FMT$ or FORMAT$??? I tried to lookup in Wiki and no reference at all. I can't be thinking of old GW BASIC? It was like PRINT USING but not necessarily for immediate PRINT or PRINT USING.

Saving an "array" (like) of values is nice added twist. Definitely bookmarking this @Qwerkey and the rest.


Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Format$ function with indexed & reusable arguments
« Reply #4 on: August 19, 2020, 07:44:06 pm »
Just updated the initial post with the most recent version, which now also supports the use of C/C++ style escape sequences.

It's ready now to be reviewed as a candidate for the Samples Gallery (@The Librarian, @bplus, @Qwerkey). Maybe a native english speaker should proof read the descriptions first.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #5 on: August 20, 2020, 10:13:41 am »
@RhoSigma  Reads fine for me, hoping someone else tries this out and reviews favorably or catches something or offers helpful criticism. The escape keys should make this even better. I never knew there was a vertical tab. I need to review the PRINT USING options to give this code more testing.

Has anyone else offered a version of FORMAT$ before this?

« Last Edit: August 20, 2020, 10:17:44 am by bplus »

FellippeHeitor

  • Guest
Re: Format$ function with indexed & reusable arguments
« Reply #6 on: August 20, 2020, 10:15:36 am »
I wrote one that would create a hidden text surface, use actual PRINT USING on it with the format and value passed and return results by reading character by character with SCREEN(). Really limited.

Code: QB64: [Select]
  1. PRINT format$("##.#", RND * 100)
  2.  
  3. FUNCTION format$ (template$, value##)
  4.     DIM tempImage&, prevDest&, prevSource&
  5.     DIM i AS INTEGER, result$
  6.  
  7.     tempImage& = _NEWIMAGE(LEN(template$) + 10, 1, 0)
  8.  
  9.     prevDest& = _DEST
  10.     prevSource& = _SOURCE
  11.     _DEST tempImage&
  12.     _SOURCE tempImage&
  13.  
  14.     PRINT USING template$; value##;
  15.     FOR i = 1 TO POS(0)
  16.         result$ = result$ + CHR$(SCREEN(1, i))
  17.     NEXT
  18.  
  19.     _DEST prevDest&
  20.     _SOURCE prevSource&
  21.     _FREEIMAGE tempImage&
  22.  
  23.     format$ = result$
« Last Edit: August 20, 2020, 11:00:11 am by FellippeHeitor »

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #7 on: August 20, 2020, 03:37:51 pm »
@FellippeHeitor  nice way to get numbers formatted, commas, decimals places...  :)


Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Format$ function with indexed & reusable arguments
« Reply #8 on: August 22, 2020, 07:27:04 pm »
Once more updated the codebox in the first post,
the IndexFormat$() function can now format up to 36 arguments in one call. You use the regular argument indices first (ie. 0{..} to 9{..}), but if you need more arguments, then you may continue using letters as indices (ie. A{..} to Z{..}), which represent the argument index numbers 10-36 respectively. The written case of the letters doesn't matter.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #9 on: August 24, 2020, 12:01:44 pm »
Well I added this gem to Informatics library before it gets buried by other programs and time or forgetfulness.
 https://www.qb64.org/forum/index.php?topic=2946.msg122030
« Last Edit: August 24, 2020, 01:15:59 pm by bplus »

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Format$ function with indexed & reusable arguments
« Reply #10 on: August 24, 2020, 12:56:56 pm »
Well, more than just a repla.., eahhh, ..Thank You B+ :)
Hope it will draw useful for many people.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #11 on: August 24, 2020, 03:00:32 pm »
Here is Fellippe like FORMAT$ wrapper tested for Single Type that might be used with IndexFormat$:
Code: QB64: [Select]
  1. _TITLE "FORMAT$ Wrapper Test - for quick number format" 'b+ 2020-08-24
  2.  
  3. 'try rounding everything to 2 digits, commas every 3 integers
  4. sngFmt$ = "\t\x220{" + STRING$(12, "#") + ",.##}\x22" ' oh let's try \x22 for quotes
  5.  
  6. 'print the single type inside quotes with number format using commas and 2 decimal place then next line
  7.  
  8. 'let's see
  9.     CLS
  10.     FOR i = 1 TO 20 'want to see if we can  make pretty column of numbers
  11.         ' build random number type single
  12.         IF RND < .5 THEN ma = -1 ELSE ma = 1
  13.         IF RND < .5 THEN mae = -1 ELSE mae = 1
  14.         ea = mae * INT(10 * RND)
  15.         a = ma * RND * 10 ^ ea
  16.         PRINT FORMAT$(sngFmt$, a), "The number was:"; a
  17.     NEXT
  18.     PRINT: PRINT "Press any to continue... esc to quit"
  19.     SLEEP
  20.  
  21.  
  22. 'Notes on PRINT USING from wiki:
  23. 'ref http://qb64.org/wiki/PRINT_USING
  24. '   & Prints an entire string value. STRING length should be limited as template width will vary.
  25. '   \  \  Denotes the start and end point of a fixed string area with spaces between(LEN = spaces + 2).
  26. '   ! Prints only the leading character of a string value. Exclamation points require underscore prefix.
  27. '   # Denotes a numerical digit. An appropriate number of digits should be used for values received.
  28. ' ^^^^    After # digits prints numerical value in exponential E+xx format. Use ^^^^^ for E+xxx values.*
  29. '   . Period sets a number's decimal point position. Digits following determine rounded value accuracy.
  30. '   ,.    Comma to left of decimal point, prints a comma every 3 used # digit places left of the decimal point.
  31. '   + Plus sign denotes the position of the number's sign. + or - will be displayed.
  32. '   - Minus sign (dash) placed after the number, displays only a negative value's sign.
  33. '   $$    Prints a dollar sign immediately before the highest non-zero # digit position of the numerical value.
  34. '   **    Prints an asterisk in any leading empty spaces of a numerical value. Adds 2 extra digit positions.
  35. '  **$    Combines ** and $$. Negative values will display minus sign to left of $.
  36. '  _ Underscore preceding a format symbol prints those symbols as literal string characters.
  37.  
  38.  
  39. 'quick wrapper function intended for numbers
  40. FUNCTION FORMAT$ (template$, number) 'AS you have your default type set
  41.     FORMAT$ = IndexFormat$(template$, _TRIM$(STR$(number)), "|") '3rd argument is dummy
  42.  
  43.  
  44. '-------------------
  45. '--- IndexFormat ---
  46. '-------------------
  47. ' This function will perform PRINT USING like formatting and return the
  48. ' formatted output string, so you can either store it in a variable or
  49. ' print it directly. An extension to the standard PRINT USING conventions
  50. ' used by IndexFormat() is argument position specification. Specifying
  51. ' the argument position lets the order of the various format tokens change
  52. ' while the arguments provided can remain the same.
  53. '
  54. ' Have a look on the following examples:
  55. '   Assuming head = 1, hands = 2 and fingers = 10,
  56. '     PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
  57. '     PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
  58. '     PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands",_
  59. '               STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  60. '  These three statements would produce the following output:
  61. '     " 1 head,  2 hands and 10 fingers" (args order matches format)
  62. '     " 1 fingers,  2 head and 10 hands" (args order wrong for format)
  63. '     "10 fingers,  1 head and  2 hands" (indexed, format picks right args)
  64. '
  65. ' Even better, you may reuse any arguments without providing them
  66. ' several times:
  67. '     PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, " +_
  68. '               "also 1{##} feet and 2{##} toes",_
  69. '               STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  70. '  This would produce the following output:
  71. '     " 1 head,  2 hands and 10 fingers, also  2 feet and 10 toes"
  72. '
  73. ' The argument positioning feature lets you change the format string
  74. ' being processed while keeping the arguments stream the same. This is an
  75. ' invaluable tool especially when localizing your programs, as it helps
  76. ' a lot when translating strings to different languages and the sentence
  77. ' structure and thus the order of the arguments changes.
  78. '
  79. ' To make the use of this function even more convenient, it does also
  80. ' allow for escape sequences as known from C/C++, so you may use those
  81. ' sequences within your format strings:
  82. '     fmt$ = "Column-1" + CHR$(9) + "Column-2"  (bulky way adding CHR$(9))
  83. '     fmt$ = "Column-1\tColumn-2"               (easy with \t sequence)
  84. ' Unfortunately you can't use \" to insert a quotation mark, as the QB64
  85. ' compiler would see the used " in the sequence as the regular end of
  86. ' the format string. However, you can use the octal or hex representations
  87. ' \042 or \x22 of the quotation mark:
  88. '     fmt$ = "This is a \x22quoted\x22 section."
  89. '
  90. ' The following sequences are available for use with this function:
  91. '     \a = CHR$(7) ' audio bell             \b = CHR$(8) ' backspace
  92. '     \t = CHR$(9) ' tabulator              \n = CHR$(10) 'line feed
  93. '     \v = CHR$(11) 'vertical tabulator     \f = CHR$(12) 'form feed
  94. '     \r = CHR$(13) 'carriage return        \e = CHR$(27) 'escape
  95. '           \nnn = octal ASCII code (    3 digits, eg. \042)
  96. '           \xnn = hex ASCII code   (x + 2 digits, eg. \x3F)
  97. '----------
  98. ' SYNTAX:
  99. '   formatted$ = IndexFormat$ (fmt$, arg$, sep$)
  100. '----------
  101. ' INPUTS:
  102. '   --- fmt$ ---
  103. '    The format string as usual for PRINT USING, but all format symbols
  104. '    enclosed in curly brackets with a leading one digit index number,
  105. '    which designates the argument to use (ie. 0{..} to 9{..}). You may
  106. '    continue with indices A{..} to Z{..}, if you need more arguments.
  107. '    All argument indices may be reused as often as you want. Missing
  108. '    arguments will be processed as empty or zero, no error will occur.
  109. '    You may also use C/C++ style escape sequences (\t, \n, \r etc.).
  110. '    Write _{, _} and _\ to get literal curly brackets or a backslash in
  111. '    the output and __ to get an underscore. No underscores are allowed
  112. '    inside the {} tokens, these should contain the format symbols only.
  113. '   --- arg$ ---
  114. '    This is a concatenated string of all arguments separated by a user
  115. '    chosen char. Use STR$(numVar) to add in a numeric variable. It won't
  116. '    hurt, if there are more arguments than used in the format string.
  117. '   --- sep$ ---
  118. '    The char used to separate the arguments in the concatenated argument
  119. '    string. Choose a char, which is not used in the arguments itself.
  120. '    If you need all printable chars, then CHR$(0) might be suitable.
  121. '----------
  122. ' RESULT:
  123. '   --- formatted$ ---
  124. '    Regularly the formatted string on success. If there's a problem with
  125. '    your provided format string, then a appropriate error message will
  126. '    be returned by default. If you don't like that and rather want the
  127. '    function to return an empty string on failure, then simply adjust
  128. '    the "CONST errMsg" line right below to switch the error behavior.
  129. '---------------------------------------------------------------------
  130. FUNCTION IndexFormat$ (fmt$, arg$, sep$)
  131.     '--- set your desired error behavior ---
  132.     CONST errMsg = "ON" 'ON = return error messages, OFF = return empty
  133.     '--- option _explicit requirements ---
  134.     DIM args$, shan&, dhan&, than&, i%, spos&, res$, idx%
  135.     DIM ft$, tok%, lit%, cpos&, cch$, nch$, ech$, pch$, tmp$
  136.     '--- init ---
  137.     args$ = arg$ 'avoid side effects
  138.     shan& = _SOURCE: dhan& = _DEST: than& = _NEWIMAGE(256, 1, 0)
  139.     _SOURCE than&: _DEST than&
  140.     REDIM argArr$(0 TO 35) 'all empty
  141.     '--- parse arguments ---
  142.     IF RIGHT$(args$, 1) <> sep$ THEN args$ = args$ + sep$
  143.     FOR i% = 0 TO 35
  144.         spos& = INSTR(args$, sep$)
  145.         IF spos& = 0 THEN EXIT FOR
  146.         argArr$(i%) = LEFT$(args$, spos& - 1)
  147.         args$ = MID$(args$, spos& + 1)
  148.     NEXT i%
  149.     '--- process format string ---
  150.     res$ = "": idx% = -1: ft$ = "": tok% = 0: lit% = 0
  151.     FOR cpos& = 1 TO LEN(fmt$)
  152.         cch$ = MID$(fmt$, cpos&, 1)
  153.         IF cch$ = "_" AND lit% = 0 THEN
  154.             IF NOT tok% THEN
  155.                 lit% = -1
  156.             ELSE
  157.                 res$ = "ERROR: No _ allowed inside {} in fmt$ at position" + STR$(cpos&) + " !!"
  158.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  159.                 EXIT FOR
  160.             END IF
  161.         ELSEIF cch$ = "\" AND lit% = 0 AND tok% = 0 THEN
  162.             IF cpos& < LEN(fmt$) THEN
  163.                 nch$ = MID$(fmt$, cpos& + 1, 1)
  164.                 SELECT CASE UCASE$(nch$)
  165.                     CASE "A": ech$ = CHR$(7) ' audio bell
  166.                     CASE "B": ech$ = CHR$(8) ' backspace
  167.                     CASE "T": ech$ = CHR$(9) ' tabulator
  168.                     CASE "N": ech$ = CHR$(10) 'line feed
  169.                     CASE "V": ech$ = CHR$(11) 'vertical tabulator
  170.                     CASE "F": ech$ = CHR$(12) 'form feed
  171.                     CASE "R": ech$ = CHR$(13) 'carriage return
  172.                     CASE "E": ech$ = CHR$(27) 'escape
  173.                     CASE "0", "1", "2", "3" '  octal ASCII (3 digits)
  174.                         ech$ = CHR$(VAL("&O" + MID$(fmt$, cpos& + 1, 3)))
  175.                         cpos& = cpos& + 2
  176.                     CASE "X" '                 hex ASCII (x + 2 digits)
  177.                         ech$ = CHR$(VAL("&H" + MID$(fmt$, cpos& + 2, 2)))
  178.                         cpos& = cpos& + 2
  179.                 END SELECT
  180.                 res$ = res$ + ech$
  181.                 cpos& = cpos& + 1
  182.             END IF
  183.         ELSEIF cch$ = "{" AND lit% = 0 THEN
  184.             pch$ = UCASE$(MID$(fmt$, cpos& - 1, 1))
  185.             IF (pch$ >= "0" AND pch$ <= "9") OR (pch$ >= "A" AND pch$ <= "Z") THEN
  186.                 IF idx% = -1 THEN
  187.                     res$ = LEFT$(res$, LEN(res$) - 1)
  188.                     idx% = VAL(pch$): tok% = -1
  189.                     IF idx% = 0 AND pch$ <> "0" THEN idx% = ASC(pch$) - 55
  190.                 ELSE
  191.                     res$ = "ERROR: Unexpected { in fmt$ at position" + STR$(cpos&) + " !!"
  192.                     IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  193.                     EXIT FOR
  194.                 END IF
  195.             ELSE
  196.                 res$ = "ERROR: Missing index before { in fmt$ at position" + STR$(cpos&) + " !!"
  197.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  198.                 EXIT FOR
  199.             END IF
  200.         ELSEIF cch$ = "}" AND lit% = 0 THEN
  201.             IF idx% >= 0 THEN
  202.                 GOSUB doArgFormat: res$ = res$ + tmp$
  203.                 idx% = -1: ft$ = "": tok% = 0
  204.             ELSE
  205.                 res$ = "ERROR: Unexpected } in fmt$ at position" + STR$(cpos&) + " !!"
  206.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  207.                 EXIT FOR
  208.             END IF
  209.         ELSE
  210.             IF tok% THEN ft$ = ft$ + cch$: ELSE res$ = res$ + cch$
  211.             lit% = 0
  212.         END IF
  213.     NEXT cpos&
  214.     '--- cleanup & set result ---
  215.     ERASE argArr$
  216.     _SOURCE shan&: _DEST dhan&: _FREEIMAGE than&
  217.     IndexFormat$ = res$
  218.     '-----------------------------
  219.     doArgFormat:
  220.     CLS: tmp$ = ""
  221.     IF ft$ = "" THEN RETURN
  222.     SELECT CASE LEFT$(ft$, 1)
  223.         CASE "!", "&", "\": PRINT USING ft$; argArr$(idx%);
  224.         CASE ELSE: PRINT USING ft$; VAL(argArr$(idx%));
  225.     END SELECT
  226.     FOR i% = 1 TO POS(0) - 1
  227.         tmp$ = tmp$ + CHR$(SCREEN(1, i%))
  228.     NEXT i%
  229.     RETURN
  230.  

Kinda elaborate sngFmt$ but I wanted to test tab and chr$(34) handling, it passed! :)

EDIT: replace CHR$(34) with \x22 for double quotes
« Last Edit: August 24, 2020, 03:21:26 pm by bplus »

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Format$ function with indexed & reusable arguments
« Reply #12 on: August 27, 2020, 09:04:39 am »
One final update to the first post.

The thing I've always missed in the PRINT USING formatting, bin/hex/oct number formatting, is now finally implemented into my IndexFormat() function. I also rewrote large parts of the descriptions to remove unnecessary redundant information and to make it (hopefully) easier to follow and undertandable.

@bplus
Please be so kind to update the entry in the Samples Gallery.
You need to update the codebox as well as the regular text you've quoted from the first post, both have changed.
Thanks in advance.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack

Offline bplus

  • Global Moderator
  • Forum Resident
  • Posts: 8053
  • b = b + ...
Re: Format$ function with indexed & reusable arguments
« Reply #13 on: August 27, 2020, 01:14:11 pm »
OK @RhoSigma

For a test, I revised my FORMAT$ number function to do digits to left and right of decimal with commas. The first test looks pretty good but I test a literal and eh... Max 16 digits are kept by QB64.

Code: QB64: [Select]
  1. _TITLE "FORMAT$ Wrapper Test - for quick number format" 'b+ 2020-08-24
  2. '2020-08-27  revise FORMAT$ to take arguments for digits left and right of decimal
  3.  
  4. 'let's see
  5.     CLS
  6.     FOR i = 1 TO 10 'want to see if we can  make pretty column of numbers
  7.         ' build random number type single
  8.         IF RND < .5 THEN ma = -1 ELSE ma = 1
  9.         IF RND < .5 THEN mae = -1 ELSE mae = 1
  10.         ea = mae * INT(10 * RND)
  11.         a = ma * RND * 10 ^ ea
  12.         PRINT "12345678901234567890" ' ruler
  13.         PRINT FORMAT$(12, 4, a), "The number was:"; a
  14.     NEXT
  15.     PRINT: PRINT "Press any to continue... esc for one last test"
  16.     SLEEP
  17. PRINT "DIM n AS _FLOAT"
  18. PRINT "n = 111987654321.123456789"
  19. PRINT "PRINT FORMAT$(15, 8, n)"
  20. n = 111987654321.123456789
  21. PRINT "n = "; FORMAT$(15, 8, n)
  22. PRINT "Yes, 16 digits is maximum you can keep."
  23.  
  24. 'Notes on PRINT USING from wiki:
  25. 'ref http://qb64.org/wiki/PRINT_USING
  26. '   & Prints an entire string value. STRING length should be limited as template width will vary.
  27. '   \  \  Denotes the start and end point of a fixed string area with spaces between(LEN = spaces + 2).
  28. '   ! Prints only the leading character of a string value. Exclamation points require underscore prefix.
  29. '   # Denotes a numerical digit. An appropriate number of digits should be used for values received.
  30. ' ^^^^    After # digits prints numerical value in exponential E+xx format. Use ^^^^^ for E+xxx values.*
  31. '   . Period sets a number's decimal point position. Digits following determine rounded value accuracy.
  32. '   ,.    Comma to left of decimal point, prints a comma every 3 used # digit places left of the decimal point.
  33. '   + Plus sign denotes the position of the number's sign. + or - will be displayed.
  34. '   - Minus sign (dash) placed after the number, displays only a negative value's sign.
  35. '   $$    Prints a dollar sign immediately before the highest non-zero # digit position of the numerical value.
  36. '   **    Prints an asterisk in any leading empty spaces of a numerical value. Adds 2 extra digit positions.
  37. '  **$    Combines ** and $$. Negative values will display minus sign to left of $.
  38. '  _ Underscore preceding a format symbol prints those symbols as literal string characters.
  39.  
  40.  
  41. 'quick wrapper function intended for numbers
  42. FUNCTION FORMAT$ (maxDigitsLeft AS INTEGER, digitsRight AS INTEGER, number AS _FLOAT)
  43.     FORMAT$ = IndexFormat$("0{" + STRING$(maxDigitsLeft, "#") + ",." + STRING$(digitsRight, "#") + "}", _TRIM$(STR$(number)), "|") '3rd argument is dummy
  44.  
  45.  
  46.  
  47. '-------------------
  48. '--- IndexFormat ---  2020-08-27
  49. '-------------------
  50. ' This function will perform PRINT USING like formatting and return the
  51. ' formatted output string, so you can either store it in a variable or
  52. ' print it directly. However, compared to the PRINT USING conventions,
  53. ' this function has several extensions. The first and most important is
  54. ' argument position specification (indexing). That is, each formatting
  55. ' token used in the format template knows to which argument it belongs,
  56. ' hence the order of the various format tokens can be changed without
  57. ' the need of reordering the provided arguments too. Second extension,
  58. ' with indexing all arguments can be easily reused, just by using the
  59. ' same index number for multiple format tokens. It is not required to
  60. ' pass the reused arguments multiple times to the function. Indexing is
  61. ' done really simple in the form IS{PUFS}, where IS = index specifier
  62. ' (0-9/A-Z, hence 36 args max.) and PUFS = PRINT USING format symbols.
  63. '
  64. ' Have a look on the following examples:
  65. ' --------------------------------------
  66. ' Assuming head = 1, hands = 2 and fingers = 10,
  67. '   PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
  68. '   PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
  69. '   PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands",_
  70. '             STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  71. '
  72. ' These three statements would produce the following output:
  73. '   " 1 head,  2 hands and 10 fingers" (args order matches template)
  74. '   " 1 fingers,  2 head and 10 hands" (args order wrong for template)
  75. '   "10 fingers,  1 head and  2 hands" (indexed, tokens pick right args)
  76. '
  77. ' Reuse any arguments without providing them multiple times:
  78. ' ----------------------------------------------------------
  79. '   PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, " +_
  80. '             "also 1{##} feet and 2{##} toes",_
  81. '             STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  82. '
  83. ' This would produce the following output:
  84. '   " 1 head,  2 hands and 10 fingers, also  2 feet and 10 toes"
  85. '
  86. ' Another extension are three new PUFS to format numbers into binary,
  87. ' hexadecimal or octal notation, optional with a given field length.
  88. ' The symbols are B/b, H/h and O/o respectively. For hexadecimal the
  89. ' case of the symbol determines whether to use hex letters A-F or a-f.
  90. ' The optional field length directly follows the symbol given as decimal
  91. ' number (eg. {H8} = hex uppercase 8 digits, {B16} = binary 16 digits).
  92. ' If length is used, then the field is filled with leading zeros. If
  93. ' the field is to short, then it is extended to the minimum required
  94. ' for the number and prefixed with a % as usual. If length is omitted,
  95. ' then the output length is variable just like for strings (& symbol),
  96. ' usually the minimum required number of digits for the given number.
  97. '
  98. ' To make writing the format templates somewhat more convenient, there
  99. ' is one final extension, which allows the use of escape sequences as
  100. ' known from C/C++ and other programming languages.
  101. '
  102. ' Using escape sequences within format templates:
  103. ' -----------------------------------------------
  104. '   fmt$ = "Column-1" + CHR$(9) + "Column-2"   (bulky way adding CHR$(9))
  105. '   fmt$ = "Column-1\tColumn-2"                (easy with \t sequence)
  106. '   fmt$ = "This is a \x22quoted\x22 section." (quotation with \x22)
  107. '
  108. ' Unfortunately we can't use \" to insert a quotation mark, as the QB64
  109. ' compiler would see the " used in the sequence as the regular end of
  110. ' the template string. However, as shown in the last example we can use
  111. ' the octal or hex representations \042 or \x22 of the quotation mark.
  112. '
  113. ' The following sequences are available for use with this function:
  114. ' -----------------------------------------------------------------
  115. '   \a = CHR$(7) ' audio bell             \b = CHR$(8) ' backspace
  116. '   \t = CHR$(9) ' tabulator              \n = CHR$(10) 'line feed
  117. '   \v = CHR$(11) 'vertical tabulator     \f = CHR$(12) 'form feed
  118. '   \r = CHR$(13) 'carriage return        \e = CHR$(27) 'escape
  119. '         \nnn = octal ASCII code (    3 digits, eg. \042)
  120. '         \xnn = hex ASCII code   (x + 2 digits, eg. \x3F)
  121. '
  122. '----------
  123. ' SYNTAX:
  124. '   formatted$ = IndexFormat$ (fmt$, arg$, sep$)
  125. '
  126. '----------
  127. ' INPUTS:
  128. '   --- fmt$ ---
  129. '    The format template as usual for PRINT USING, but all format tokens
  130. '    must be indexed, ie. enclosed in curly brackets with a leading one
  131. '    digit index number/letter, which designates the argument to use.
  132. '    All argument indices may be reused as often as you want. Missing
  133. '    arguments will be processed as empty or zero, no error will occur.
  134. '    You may also use C/C++ style escape sequences. Write _{, _} and _\
  135. '    to get literal curly brackets or a backslash in the output and __
  136. '    to get an underscore. All other format symbols doesn't need to be
  137. '    underscored anymore to make them literals. Inside the {} tokens no
  138. '    underscores are allowed at all, these can contain the PRINT USING
  139. '    or the new B/H/O format symbols only.
  140. '   --- arg$ ---
  141. '    This is a concatenated string of all arguments separated by a user
  142. '    chosen char. Use STR$(numVar) to add in a numeric variable. It won't
  143. '    hurt, if there are more arguments than used in the format template.
  144. '   --- sep$ ---
  145. '    The char used to separate the arguments in the concatenated argument
  146. '    string. Choose a char, which is not used in the arguments itself.
  147. '    If you need all printable chars, then CHR$(0) might be suitable.
  148. '    This is required, even if you pass a single argument only.
  149. '
  150. '----------
  151. ' RESULT:
  152. '   --- formatted$ ---
  153. '    Regularly the formatted string on success. If there's a problem with
  154. '    your provided format template, then a appropriate error message will
  155. '    be returned by default. If you don't like that and rather want the
  156. '    function to return an empty string on failure, then simply adjust
  157. '    the "CONST errMsg" line right below to switch the error behavior.
  158. '---------------------------------------------------------------------
  159. FUNCTION IndexFormat$ (fmt$, arg$, sep$)
  160.     '--- set your desired error behavior ---
  161.     CONST errMsg = "ON" 'ON = return error messages, OFF = return empty
  162.     '--- option _explicit requirements ---
  163.     DIM args$, shan&, dhan&, than&, i%, spos&, res$, idx%
  164.     DIM ft$, tok%, lit%, cpos&, cch$, nch$, ech$, pch$, tmp$
  165.     DIM typ$, tyl%, value&&, temp~&&, charPos%, highPos%
  166.     '--- init ---
  167.     args$ = arg$ 'avoid side effects
  168.     shan& = _SOURCE: dhan& = _DEST: than& = _NEWIMAGE(256, 1, 0)
  169.     _SOURCE than&: _DEST than&
  170.     REDIM argArr$(0 TO 35) 'all empty
  171.     '--- parse arguments ---
  172.     IF RIGHT$(args$, 1) <> sep$ THEN args$ = args$ + sep$
  173.     FOR i% = 0 TO 35
  174.         spos& = INSTR(args$, sep$)
  175.         IF spos& = 0 THEN EXIT FOR
  176.         argArr$(i%) = LEFT$(args$, spos& - 1)
  177.         args$ = MID$(args$, spos& + 1)
  178.     NEXT i%
  179.     '--- process format template ---
  180.     res$ = "": idx% = -1: ft$ = "": tok% = 0: lit% = 0
  181.     FOR cpos& = 1 TO LEN(fmt$)
  182.         cch$ = MID$(fmt$, cpos&, 1)
  183.         IF cch$ = "_" AND lit% = 0 THEN 'next symbol is literal
  184.             IF NOT tok% THEN
  185.                 lit% = -1
  186.             ELSE
  187.                 res$ = "ERROR: No _ allowed inside {} in fmt$ at position" + STR$(cpos&) + " !!"
  188.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  189.                 EXIT FOR
  190.             END IF
  191.         ELSEIF cch$ = "\" AND lit% = 0 AND tok% = 0 THEN 'insert esc sequence
  192.             IF cpos& < LEN(fmt$) THEN
  193.                 nch$ = MID$(fmt$, cpos& + 1, 1)
  194.                 SELECT CASE UCASE$(nch$)
  195.                     CASE "A": ech$ = CHR$(7) ' audio bell
  196.                     CASE "B": ech$ = CHR$(8) ' backspace
  197.                     CASE "T": ech$ = CHR$(9) ' tabulator
  198.                     CASE "N": ech$ = CHR$(10) 'line feed
  199.                     CASE "V": ech$ = CHR$(11) 'vertical tabulator
  200.                     CASE "F": ech$ = CHR$(12) 'form feed
  201.                     CASE "R": ech$ = CHR$(13) 'carriage return
  202.                     CASE "E": ech$ = CHR$(27) 'escape
  203.                     CASE "0", "1", "2", "3" '  octal ASCII (3 digits)
  204.                         ech$ = CHR$(VAL("&O" + MID$(fmt$, cpos& + 1, 3)))
  205.                         cpos& = cpos& + 2
  206.                     CASE "X" '                 hex ASCII (x + 2 digits)
  207.                         ech$ = CHR$(VAL("&H" + MID$(fmt$, cpos& + 2, 2)))
  208.                         cpos& = cpos& + 2
  209.                 END SELECT
  210.                 res$ = res$ + ech$
  211.                 cpos& = cpos& + 1
  212.             END IF
  213.         ELSEIF cch$ = "{" AND lit% = 0 THEN 'begin of formatting token
  214.             pch$ = UCASE$(MID$(fmt$, cpos& - 1, 1))
  215.             IF (pch$ >= "0" AND pch$ <= "9") OR (pch$ >= "A" AND pch$ <= "Z") THEN
  216.                 IF idx% = -1 THEN
  217.                     res$ = LEFT$(res$, LEN(res$) - 1)
  218.                     idx% = VAL(pch$): tok% = -1
  219.                     IF idx% = 0 AND pch$ <> "0" THEN idx% = ASC(pch$) - 55
  220.                 ELSE
  221.                     res$ = "ERROR: Unexpected { in fmt$ at position" + STR$(cpos&) + " !!"
  222.                     IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  223.                     EXIT FOR
  224.                 END IF
  225.             ELSE
  226.                 res$ = "ERROR: Missing index before { in fmt$ at position" + STR$(cpos&) + " !!"
  227.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  228.                 EXIT FOR
  229.             END IF
  230.         ELSEIF cch$ = "}" AND lit% = 0 THEN 'end of formatting token
  231.             IF idx% >= 0 THEN
  232.                 GOSUB doArgFormat: res$ = res$ + tmp$
  233.                 idx% = -1: ft$ = "": tok% = 0
  234.             ELSE
  235.                 res$ = "ERROR: Unexpected } in fmt$ at position" + STR$(cpos&) + " !!"
  236.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  237.                 EXIT FOR
  238.             END IF
  239.         ELSE 'take literal char/symbol
  240.             IF tok% THEN ft$ = ft$ + cch$: ELSE res$ = res$ + cch$
  241.             lit% = 0
  242.         END IF
  243.     NEXT cpos&
  244.     '--- cleanup & set result ---
  245.     ERASE argArr$
  246.     _SOURCE shan&: _DEST dhan&: _FREEIMAGE than&
  247.     IndexFormat$ = res$
  248.     '-----------------------------
  249.     doArgFormat:
  250.     CLS: tmp$ = ""
  251.     IF ft$ = "" THEN RETURN
  252.     SELECT CASE UCASE$(LEFT$(ft$, 1))
  253.         CASE "!", "&", "\": PRINT USING ft$; argArr$(idx%); 'string
  254.         CASE "B", "H", "O" 'bin/hex/oct
  255.             typ$ = LEFT$(ft$, 1): tyl% = VAL(MID$(ft$, 2))
  256.             SELECT CASE typ$
  257.                 CASE "B", "b": GOSUB doBinString
  258.                 CASE "H", "h"
  259.                     tmp$ = HEX$(VAL(argArr$(idx%)))
  260.                     IF typ$ = "H" THEN tmp$ = UCASE$(tmp$): ELSE tmp$ = LCASE$(tmp$)
  261.                 CASE "O", "o": tmp$ = OCT$(VAL(argArr$(idx%)))
  262.             END SELECT
  263.             IF tyl% > 0 THEN
  264.                 IF LEN(tmp$) <= tyl% THEN
  265.                     tmp$ = RIGHT$(STRING$(tyl%, "0") + tmp$, tyl%)
  266.                 ELSE
  267.                     tmp$ = "%" + tmp$
  268.                 END IF
  269.             END IF
  270.             RETURN
  271.         CASE ELSE: PRINT USING ft$; VAL(argArr$(idx%)); 'number
  272.     END SELECT
  273.     FOR i% = 1 TO POS(0) - 1
  274.         tmp$ = tmp$ + CHR$(SCREEN(1, i%))
  275.     NEXT i%
  276.     RETURN
  277.     '-----------------------------
  278.     doBinString:
  279.     value&& = VAL(argArr$(idx%))
  280.     temp~&& = value&&
  281.     tmp$ = STRING$(64, "0"): charPos% = 64: highPos% = 64
  282.     DO
  283.         IF (temp~&& AND 1) THEN MID$(tmp$, charPos%, 1) = "1": highPos% = charPos%
  284.         charPos% = charPos% - 1: temp~&& = temp~&& \ 2
  285.     LOOP UNTIL temp~&& = 0
  286.     IF value&& < 0 THEN
  287.         IF -value&& < &H0080000000~&& THEN highPos% = 33
  288.         IF -value&& < &H0000008000~&& THEN highPos% = 49
  289.         IF -value&& < &H0000000080~&& THEN highPos% = 57
  290.     END IF
  291.     tmp$ = MID$(tmp$, highPos%)
  292.     RETURN
  293.  
« Last Edit: August 27, 2020, 03:51:23 pm by bplus »

Offline RhoSigma

  • QB64 Developer
  • Forum Resident
  • Posts: 565
Re: Format$ function with indexed & reusable arguments
« Reply #14 on: August 27, 2020, 04:21:54 pm »
@bplus: I'm afraid this limit is already imposed by STR$, which is used to add a numeric variable into the argument stream,
see the modification to your program:
Code: QB64: [Select]
  1. _TITLE "FORMAT$ Wrapper Test - for quick number format" 'b+ 2020-08-24
  2. '2020-08-27  revise FORMAT$ to take arguments for digits left and right of decimal
  3.  
  4. 'let's see
  5.     CLS
  6.     FOR i = 1 TO 10 'want to see if we can  make pretty column of numbers
  7.         ' build random number type single
  8.         IF RND < .5 THEN ma = -1 ELSE ma = 1
  9.         IF RND < .5 THEN mae = -1 ELSE mae = 1
  10.         ea = mae * INT(10 * RND)
  11.         a = ma * RND * 10 ^ ea
  12.         PRINT "12345678901234567890" ' ruler
  13.         PRINT FORMAT$(12, 4, a), "The number was:"; a
  14.     NEXT
  15.     PRINT: PRINT "Press any to continue... esc for one last test"
  16.     SLEEP
  17. PRINT "DIM n AS _FLOAT" ' already tried _FLOAT can't get frick'n 14 or 15 digits????????????
  18. PRINT "n = 111987654321.123456789"
  19. PRINT "PRINT STR$(n)"
  20. PRINT "PRINT FORMAT$(15, 8, n)"
  21. PRINT "PRINT FORMAT2$(15, 8, "; CHR$(34); "111987654321.123456789"; CHR$(34); ")"
  22. n = 111987654321.123456789
  23. PRINT "STR$ = "; STR$(n)
  24. PRINT "   n = "; FORMAT$(15, 8, n)
  25. PRINT "   n = "; FORMAT2$(15, 8, "111987654321.123456789")
  26. PRINT "Yes, 16 digits maximum is imposed by STR$,"
  27. PRINT "given as literal (string) number makes it work."
  28. PRINT "Also keep in mind, that the PRINT USING wiki states,"
  29. PRINT "that no more than 25 # digit places are allowed in a"
  30. PRINT "template number or an error will occur."
  31.  
  32. 'Notes on PRINT USING from wiki:
  33. 'ref http://qb64.org/wiki/PRINT_USING
  34. '   & Prints an entire string value. STRING length should be limited as template width will vary.
  35. '   \  \  Denotes the start and end point of a fixed string area with spaces between(LEN = spaces + 2).
  36. '   ! Prints only the leading character of a string value. Exclamation points require underscore prefix.
  37. '   # Denotes a numerical digit. An appropriate number of digits should be used for values received.
  38. ' ^^^^    After # digits prints numerical value in exponential E+xx format. Use ^^^^^ for E+xxx values.*
  39. '   . Period sets a number's decimal point position. Digits following determine rounded value accuracy.
  40. '   ,.    Comma to left of decimal point, prints a comma every 3 used # digit places left of the decimal point.
  41. '   + Plus sign denotes the position of the number's sign. + or - will be displayed.
  42. '   - Minus sign (dash) placed after the number, displays only a negative value's sign.
  43. '   $$    Prints a dollar sign immediately before the highest non-zero # digit position of the numerical value.
  44. '   **    Prints an asterisk in any leading empty spaces of a numerical value. Adds 2 extra digit positions.
  45. '  **$    Combines ** and $$. Negative values will display minus sign to left of $.
  46. '  _ Underscore preceding a format symbol prints those symbols as literal string characters.
  47.  
  48.  
  49. 'quick wrapper function intended for numbers
  50. FUNCTION FORMAT$ (maxDigitsLeft AS INTEGER, digitsRight AS INTEGER, number AS _FLOAT) 'AS you have your default type set
  51. FORMAT$ = IndexFormat$("0{" + STRING$(maxDigitsLeft, "#") + ",." + STRING$(digitsRight, "#") + "}", STR$(number), "|") '3rd argument is dummy
  52.  
  53. FUNCTION FORMAT2$ (maxDigitsLeft AS INTEGER, digitsRight AS INTEGER, number AS STRING) 'AS you have your default type set
  54. FORMAT2$ = IndexFormat$("0{" + STRING$(maxDigitsLeft, "#") + ",." + STRING$(digitsRight, "#") + "}", number, "|") '3rd argument is dummy
  55.  
  56.  
  57.  
  58. '-------------------
  59. '--- IndexFormat ---  2020-08-27
  60. '-------------------
  61. ' This function will perform PRINT USING like formatting and return the
  62. ' formatted output string, so you can either store it in a variable or
  63. ' print it directly. However, compared to the PRINT USING conventions,
  64. ' this function has several extensions. The first and most important is
  65. ' argument position specification (indexing). That is, each formatting
  66. ' token used in the format template knows to which argument it belongs,
  67. ' hence the order of the various format tokens can be changed without
  68. ' the need of reordering the provided arguments too. Second extension,
  69. ' with indexing all arguments can be easily reused, just by using the
  70. ' same index number for multiple format tokens. It is not required to
  71. ' pass the reused arguments multiple times to the function. Indexing is
  72. ' done really simple in the form IS{PUFS}, where IS = index specifier
  73. ' (0-9/A-Z, hence 36 args max.) and PUFS = PRINT USING format symbols.
  74. '
  75. ' Have a look on the following examples:
  76. ' --------------------------------------
  77. ' Assuming head = 1, hands = 2 and fingers = 10,
  78. '   PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
  79. '   PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
  80. '   PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands",_
  81. '             STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  82. '
  83. ' These three statements would produce the following output:
  84. '   " 1 head,  2 hands and 10 fingers" (args order matches template)
  85. '   " 1 fingers,  2 head and 10 hands" (args order wrong for template)
  86. '   "10 fingers,  1 head and  2 hands" (indexed, tokens pick right args)
  87. '
  88. ' Reuse any arguments without providing them multiple times:
  89. ' ----------------------------------------------------------
  90. '   PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, " +_
  91. '             "also 1{##} feet and 2{##} toes",_
  92. '             STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  93. '
  94. ' This would produce the following output:
  95. '   " 1 head,  2 hands and 10 fingers, also  2 feet and 10 toes"
  96. '
  97. ' Another extension are three new PUFS to format numbers into binary,
  98. ' hexadecimal or octal notation, optional with a given field length.
  99. ' The symbols are B/b, H/h and O/o respectively. For hexadecimal the
  100. ' case of the symbol determines whether to use hex letters A-F or a-f.
  101. ' The optional field length directly follows the symbol given as decimal
  102. ' number (eg. {H8} = hex uppercase 8 digits, {B16} = binary 16 digits).
  103. ' If length is used, then the field is filled with leading zeros. If
  104. ' the field is to short, then it is extended to the minimum required
  105. ' for the number and prefixed with a % as usual. If length is omitted,
  106. ' then the output length is variable just like for strings (& symbol),
  107. ' usually the minimum required number of digits for the given number.
  108. '
  109. ' To make writing the format templates somewhat more convenient, there
  110. ' is one final extension, which allows the use of escape sequences as
  111. ' known from C/C++ and other programming languages.
  112. '
  113. ' Using escape sequences within format templates:
  114. ' -----------------------------------------------
  115. '   fmt$ = "Column-1" + CHR$(9) + "Column-2"   (bulky way adding CHR$(9))
  116. '   fmt$ = "Column-1\tColumn-2"                (easy with \t sequence)
  117. '   fmt$ = "This is a \x22quoted\x22 section." (quotation with \x22)
  118. '
  119. ' Unfortunately we can't use \" to insert a quotation mark, as the QB64
  120. ' compiler would see the " used in the sequence as the regular end of
  121. ' the template string. However, as shown in the last example we can use
  122. ' the octal or hex representations \042 or \x22 of the quotation mark.
  123. '
  124. ' The following sequences are available for use with this function:
  125. ' -----------------------------------------------------------------
  126. '   \a = CHR$(7) ' audio bell             \b = CHR$(8) ' backspace
  127. '   \t = CHR$(9) ' tabulator              \n = CHR$(10) 'line feed
  128. '   \v = CHR$(11) 'vertical tabulator     \f = CHR$(12) 'form feed
  129. '   \r = CHR$(13) 'carriage return        \e = CHR$(27) 'escape
  130. '         \nnn = octal ASCII code (    3 digits, eg. \042)
  131. '         \xnn = hex ASCII code   (x + 2 digits, eg. \x3F)
  132. '
  133. '----------
  134. ' SYNTAX:
  135. '   formatted$ = IndexFormat$ (fmt$, arg$, sep$)
  136. '
  137. '----------
  138. ' INPUTS:
  139. '   --- fmt$ ---
  140. '    The format template as usual for PRINT USING, but all format tokens
  141. '    must be indexed, ie. enclosed in curly brackets with a leading one
  142. '    digit index number/letter, which designates the argument to use.
  143. '    All argument indices may be reused as often as you want. Missing
  144. '    arguments will be processed as empty or zero, no error will occur.
  145. '    You may also use C/C++ style escape sequences. Write _{, _} and _\
  146. '    to get literal curly brackets or a backslash in the output and __
  147. '    to get an underscore. All other format symbols doesn't need to be
  148. '    underscored anymore to make them literals. Inside the {} tokens no
  149. '    underscores are allowed at all, these can contain the PRINT USING
  150. '    or the new B/H/O format symbols only.
  151. '   --- arg$ ---
  152. '    This is a concatenated string of all arguments separated by a user
  153. '    chosen char. Use STR$(numVar) to add in a numeric variable. It won't
  154. '    hurt, if there are more arguments than used in the format template.
  155. '   --- sep$ ---
  156. '    The char used to separate the arguments in the concatenated argument
  157. '    string. Choose a char, which is not used in the arguments itself.
  158. '    If you need all printable chars, then CHR$(0) might be suitable.
  159. '    This is required, even if you pass a single argument only.
  160. '
  161. '----------
  162. ' RESULT:
  163. '   --- formatted$ ---
  164. '    Regularly the formatted string on success. If there's a problem with
  165. '    your provided format template, then a appropriate error message will
  166. '    be returned by default. If you don't like that and rather want the
  167. '    function to return an empty string on failure, then simply adjust
  168. '    the "CONST errMsg" line right below to switch the error behavior.
  169. '---------------------------------------------------------------------
  170. FUNCTION IndexFormat$ (fmt$, arg$, sep$)
  171. '--- set your desired error behavior ---
  172. CONST errMsg = "ON" 'ON = return error messages, OFF = return empty
  173. '--- option _explicit requirements ---
  174. DIM args$, shan&, dhan&, than&, i%, spos&, res$, idx%
  175. DIM ft$, tok%, lit%, cpos&, cch$, nch$, ech$, pch$, tmp$
  176. DIM typ$, tyl%, value&&, temp~&&, charPos%, highPos%
  177. '--- init ---
  178. args$ = arg$ 'avoid side effects
  179. shan& = _SOURCE: dhan& = _DEST: than& = _NEWIMAGE(256, 1, 0)
  180. _SOURCE than&: _DEST than&
  181. REDIM argArr$(0 TO 35) 'all empty
  182. '--- parse arguments ---
  183. IF RIGHT$(args$, 1) <> sep$ THEN args$ = args$ + sep$
  184. FOR i% = 0 TO 35
  185.     spos& = INSTR(args$, sep$)
  186.     IF spos& = 0 THEN EXIT FOR
  187.     argArr$(i%) = LEFT$(args$, spos& - 1)
  188.     args$ = MID$(args$, spos& + 1)
  189. NEXT i%
  190. '--- process format template ---
  191. res$ = "": idx% = -1: ft$ = "": tok% = 0: lit% = 0
  192. FOR cpos& = 1 TO LEN(fmt$)
  193.     cch$ = MID$(fmt$, cpos&, 1)
  194.     IF cch$ = "_" AND lit% = 0 THEN 'next symbol is literal
  195.         IF NOT tok% THEN
  196.             lit% = -1
  197.         ELSE
  198.             res$ = "ERROR: No _ allowed inside {} in fmt$ at position" + STR$(cpos&) + " !!"
  199.             IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  200.             EXIT FOR
  201.         END IF
  202.     ELSEIF cch$ = "\" AND lit% = 0 AND tok% = 0 THEN 'insert esc sequence
  203.         IF cpos& < LEN(fmt$) THEN
  204.             nch$ = MID$(fmt$, cpos& + 1, 1)
  205.             SELECT CASE UCASE$(nch$)
  206.                 CASE "A": ech$ = CHR$(7) ' audio bell
  207.                 CASE "B": ech$ = CHR$(8) ' backspace
  208.                 CASE "T": ech$ = CHR$(9) ' tabulator
  209.                 CASE "N": ech$ = CHR$(10) 'line feed
  210.                 CASE "V": ech$ = CHR$(11) 'vertical tabulator
  211.                 CASE "F": ech$ = CHR$(12) 'form feed
  212.                 CASE "R": ech$ = CHR$(13) 'carriage return
  213.                 CASE "E": ech$ = CHR$(27) 'escape
  214.                 CASE "0", "1", "2", "3" '  octal ASCII (3 digits)
  215.                     ech$ = CHR$(VAL("&O" + MID$(fmt$, cpos& + 1, 3)))
  216.                     cpos& = cpos& + 2
  217.                 CASE "X" '                 hex ASCII (x + 2 digits)
  218.                     ech$ = CHR$(VAL("&H" + MID$(fmt$, cpos& + 2, 2)))
  219.                     cpos& = cpos& + 2
  220.             END SELECT
  221.             res$ = res$ + ech$
  222.             cpos& = cpos& + 1
  223.         END IF
  224.     ELSEIF cch$ = "{" AND lit% = 0 THEN 'begin of formatting token
  225.         pch$ = UCASE$(MID$(fmt$, cpos& - 1, 1))
  226.         IF (pch$ >= "0" AND pch$ <= "9") OR (pch$ >= "A" AND pch$ <= "Z") THEN
  227.             IF idx% = -1 THEN
  228.                 res$ = LEFT$(res$, LEN(res$) - 1)
  229.                 idx% = VAL(pch$): tok% = -1
  230.                 IF idx% = 0 AND pch$ <> "0" THEN idx% = ASC(pch$) - 55
  231.             ELSE
  232.                 res$ = "ERROR: Unexpected { in fmt$ at position" + STR$(cpos&) + " !!"
  233.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  234.                 EXIT FOR
  235.             END IF
  236.         ELSE
  237.             res$ = "ERROR: Missing index before { in fmt$ at position" + STR$(cpos&) + " !!"
  238.             IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  239.             EXIT FOR
  240.         END IF
  241.     ELSEIF cch$ = "}" AND lit% = 0 THEN 'end of formatting token
  242.         IF idx% >= 0 THEN
  243.             GOSUB doArgFormat: res$ = res$ + tmp$
  244.             idx% = -1: ft$ = "": tok% = 0
  245.         ELSE
  246.             res$ = "ERROR: Unexpected } in fmt$ at position" + STR$(cpos&) + " !!"
  247.             IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  248.             EXIT FOR
  249.         END IF
  250.     ELSE 'take literal char/symbol
  251.         IF tok% THEN ft$ = ft$ + cch$: ELSE res$ = res$ + cch$
  252.         lit% = 0
  253.     END IF
  254. NEXT cpos&
  255. '--- cleanup & set result ---
  256. ERASE argArr$
  257. _SOURCE shan&: _DEST dhan&: _FREEIMAGE than&
  258. IndexFormat$ = res$
  259. '-----------------------------
  260. doArgFormat:
  261. CLS: tmp$ = ""
  262. IF ft$ = "" THEN RETURN
  263.     CASE "!", "&", "\": PRINT USING ft$; argArr$(idx%); 'string
  264.     CASE "B", "H", "O" 'bin/hex/oct
  265.         typ$ = LEFT$(ft$, 1): tyl% = VAL(MID$(ft$, 2))
  266.         SELECT CASE typ$
  267.             CASE "B", "b": GOSUB doBinString
  268.             CASE "H", "h"
  269.                 tmp$ = HEX$(VAL(argArr$(idx%)))
  270.                 IF typ$ = "H" THEN tmp$ = UCASE$(tmp$): ELSE tmp$ = LCASE$(tmp$)
  271.             CASE "O", "o": tmp$ = OCT$(VAL(argArr$(idx%)))
  272.         END SELECT
  273.         IF tyl% > 0 THEN
  274.             IF LEN(tmp$) <= tyl% THEN
  275.                 tmp$ = RIGHT$(STRING$(tyl%, "0") + tmp$, tyl%)
  276.             ELSE
  277.                 tmp$ = "%" + tmp$
  278.             END IF
  279.         END IF
  280.         RETURN
  281.     CASE ELSE: PRINT USING ft$; VAL(argArr$(idx%)); 'number
  282. FOR i% = 1 TO POS(0) - 1
  283.     tmp$ = tmp$ + CHR$(SCREEN(1, i%))
  284. NEXT i%
  285. '-----------------------------
  286. doBinString:
  287. value&& = VAL(argArr$(idx%))
  288. temp~&& = value&&
  289. tmp$ = STRING$(64, "0"): charPos% = 64: highPos% = 64
  290.     IF (temp~&& AND 1) THEN MID$(tmp$, charPos%, 1) = "1": highPos% = charPos%
  291.     charPos% = charPos% - 1: temp~&& = temp~&& \ 2
  292. LOOP UNTIL temp~&& = 0
  293. IF value&& < 0 THEN
  294.     IF -value&& < &H0080000000~&& THEN highPos% = 33
  295.     IF -value&& < &H0000008000~&& THEN highPos% = 49
  296.     IF -value&& < &H0000000080~&& THEN highPos% = 57
  297. tmp$ = MID$(tmp$, highPos%)
  298.  
  299.  

and here's the cause of it, it's the C function (libqb.cpp) called by STR$ given a _FLOAT number. As you can see, it's not implemented and simply wrapped to the DOUBLE version of STR$ instead.
Code: C: [Select]
  1. qbs *qbs_str(long double value){
  2.     //not fully implemented
  3.     return qbs_str((double)value);
  4. }
  5.  

BTW - Thanks for the Samples update, but you forgot to update the RhoSigma: "quote" text, it has changed too.
My Projects:   https://qb64forum.alephc.xyz/index.php?topic=809
GuiTools - A graphic UI framework (can do multiple UI forms/windows in one program)
Libraries - ImageProcess, StringBuffers (virt. files), MD5/SHA2-Hash, LZW etc.
Bonus - Blankers, QB64/Notepad++ setup pack