Author Topic: Format$ update by RhoSigma  (Read 300 times)

Offline bplus

  • Moderator
  • Forum Resident
  • Posts: 5004
  • B+ Knot again!
Format$ update by RhoSigma
« on: August 24, 2020, 11:55:25 AM »
Author: @RhoSigma
Source: qb64.org Forum
URL: https://www.qb64.org/forum/index.php?topic=2932.msg121898#msg121898
Version: 2020-09-10 3rd
Tag: [String Function]

Description: More than just a replacement for missing FORMAT$ function from QB of past, RhoSigma has added some features to make this function more flexible than ever. 2nd version added escape code and binary, octal and hexadecimal conversions. 3rd version double underscore no longer needed to use underscore in format and professional HTML description file.

RhoSigma:
Quote
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 is an invaluable tool e.g. when localizing your programs, as it helps when translating strings to different languages and the sentence structure and thus the order of the arguments changes. Beside the indexing, this function also allows to reuse every argument as often as you want without the need to pass the same arguments multiple times. This function also adds new specifiers to format numbers into bin/hex/oct notation and to make writing the format strings more convenient, you may also use C/C++ style escape sequences to easily insert tabs, quotes, line feeds etc. without the need to use the bulky + CHR$(n) + method.

Whether you want to format some numbers with commas or create a form letter, you should find this very well documented function a handy tool for your toolbox.

Code: QB64: [Select]
  1. ' The new format template explained (see also function description):
  2. ' ------------------------------------------------------------------
  3. '  eg. "Some literal _{text_}: 0{##}. \x221{&}\x22 2{H8}\n"
  4. '               |    |     |   | |    |       |         |
  5. '               |    +--+--+   | |    +-------+---------+
  6. '               |       |      | |            |
  7. '               |       |      | |   escape sequences can be used outside
  8. '               |       |      | |   of the {} formatting tokens
  9. '               |       |      | |
  10. '               |       |      | +-- all format symbols must be inside {}
  11. '               |       |      |
  12. '               |       |      +---- index of the argument to use for this
  13. '               |       |            token according to given args order
  14. '               |       |
  15. '               |       +---- {, } and \ need a leading underscore to get
  16. '               |             them as literals in the output, but no other
  17. '               |             symbols need the underscore anymore
  18. '               |
  19. '               +--------- all text outside {} is literal and taken as is
  20.  
  21.  
  22.  
  23. '-- You may even use different formatting for the same argument, as long
  24. '-- as its types are compatible (ie. string vs. number).
  25. dateDE$ = "Date format Germany: 0{##}. 1{&} 2{####}" ' 1{} = full string
  26. dateUS$ = "Date format US     : 1{\ \}/0{##} 2{####}" '1{} = first 3 chars only
  27. '-- You may reuse any arguments as often as you want, without the need
  28. '-- to pass the same arguments multiple times to the function. You may
  29. '-- also reuse it with different formatting in the same format template.
  30. reuse$ = "Date is 1{\ \}/0{##} 2{####}. The 9th month of the year is 1{&}."
  31.  
  32.  
  33.  
  34. '-- The easiest way to pass a variable number of arguments, which may
  35. '-- even be of different types, to a user function is using a string.
  36. '-- All arguments will be concatenated in this string, separated by a
  37. '-- designated char which does not appear in the arguments itself.
  38. '-- Strings can be added as is, numbers can be added as literal strings
  39. '-- too, or in the form STR$(variable).
  40. year% = 2020
  41. argStr$ = "10|September|" + STR$(year%)
  42. '-- In this example the | is the argument separator. Use whatever is
  43. '-- suitable for your needs, maybe even a CHR$(0).
  44.  
  45.  
  46.  
  47. '-- Now let's test the whole thing, we've different token orders in the
  48. '-- format templates, but use the same argument string for both calls.
  49. PRINT IndexFormat$(dateDE$, argStr$, "|")
  50. PRINT IndexFormat$(dateUS$, argStr$, "|")
  51. '-- And now we reuse an argument, but pass the same arguments again.
  52. '-- We don't need to add another argument for this.
  53. PRINT IndexFormat$(reuse$, argStr$, "|")
  54.  
  55. '-- And here the examples from the function description.
  56. head = 1: hands = 2: fingers = 10
  57. PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
  58. PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
  59.     PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands",_
  60.               STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  61.     PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, " +_
  62.               "also 1{##} feet and 2{##} toes",_
  63.               STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
  64.  
  65. '-- The function can also handle escape sequences as known from C/C++,
  66. '-- so you may use those sequences within your format templates.
  67.     PRINT IndexFormat$("Column-1\tColumn-2\tColumn-3\n0{#.##}\t\t1{#.##}\t\t2{#.##}",_
  68.               "1.11|2.22|3.33", "|")
  69. PRINT IndexFormat$("This is a \x220{&}\x22 section.", "quoted", "|")
  70. '-- Using escape sequences and the new bin/hex/oct formatting, while
  71. '-- reusing the same argument for all tokens.
  72. PRINT IndexFormat$("Dec: 0{####}\nBin: 0{B12}\nHex: 0{H4}\nOct: 0{O}\n", "2020", "|")
  73.  
  74. '-- done
  75.  
  76.  
  77.  
  78. '--- Full description available in separate HTML document.
  79. '---------------------------------------------------------------------
  80. FUNCTION IndexFormat$ (fmt$, arg$, sep$)
  81. '--- set your desired error behavior ---
  82. CONST errMsg = "ON" 'ON = return error messages, OFF = return empty
  83. '--- option _explicit requirements ---
  84. DIM args$, shan&, dhan&, than&, i%, spos&, res$, idx%
  85. DIM ft$, tok%, lit%, cpos&, cch$, nch$, ech$, pch$, tmp$
  86. DIM typ$, tyl%, value&&, temp~&&, charPos%, highPos%
  87. '--- init ---
  88. args$ = arg$ 'avoid side effects
  89. shan& = _SOURCE: dhan& = _DEST: than& = _NEWIMAGE(256, 1, 0)
  90. _SOURCE than&: _DEST than&
  91. REDIM argArr$(0 TO 35) 'all empty
  92. '--- parse arguments ---
  93. IF RIGHT$(args$, 1) <> sep$ THEN args$ = args$ + sep$
  94. FOR i% = 0 TO 35
  95.     spos& = INSTR(args$, sep$)
  96.     IF spos& = 0 THEN EXIT FOR
  97.     argArr$(i%) = LEFT$(args$, spos& - 1)
  98.     args$ = MID$(args$, spos& + 1)
  99. NEXT i%
  100. '--- process format template ---
  101. res$ = "": idx% = -1: ft$ = "": tok% = 0: lit% = 0
  102. FOR cpos& = 1 TO LEN(fmt$)
  103.     cch$ = MID$(fmt$, cpos&, 1)
  104.     IF cch$ = "_" AND lit% = 0 THEN 'next symbol is literal
  105.         IF NOT tok% THEN
  106.             lit% = -1
  107.         ELSE
  108.             res$ = "ERROR: No _ allowed inside {} in fmt$ at position" + STR$(cpos&) + " !!"
  109.             IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  110.             EXIT FOR
  111.         END IF
  112.     ELSEIF cch$ = "\" AND lit% = 0 AND tok% = 0 THEN 'insert esc sequence
  113.         IF cpos& < LEN(fmt$) THEN
  114.             nch$ = MID$(fmt$, cpos& + 1, 1)
  115.             SELECT CASE UCASE$(nch$)
  116.                 CASE "A": ech$ = CHR$(7) ' audio bell
  117.                 CASE "B": ech$ = CHR$(8) ' backspace
  118.                 CASE "T": ech$ = CHR$(9) ' tabulator
  119.                 CASE "N": ech$ = CHR$(10) 'line feed
  120.                 CASE "V": ech$ = CHR$(11) 'vertical tabulator
  121.                 CASE "F": ech$ = CHR$(12) 'form feed
  122.                 CASE "R": ech$ = CHR$(13) 'carriage return
  123.                 CASE "E": ech$ = CHR$(27) 'escape
  124.                 CASE "0", "1", "2", "3" '  octal ASCII (3 digits)
  125.                     ech$ = CHR$(VAL("&O" + MID$(fmt$, cpos& + 1, 3)))
  126.                     cpos& = cpos& + 2
  127.                 CASE "X" '                 hex ASCII (x + 2 digits)
  128.                     ech$ = CHR$(VAL("&H" + MID$(fmt$, cpos& + 2, 2)))
  129.                     cpos& = cpos& + 2
  130.             END SELECT
  131.             res$ = res$ + ech$
  132.             cpos& = cpos& + 1
  133.         END IF
  134.     ELSEIF cch$ = "{" AND lit% = 0 THEN 'begin of formatting token
  135.         pch$ = UCASE$(MID$(fmt$, cpos& - 1, 1))
  136.         IF (pch$ >= "0" AND pch$ <= "9") OR (pch$ >= "A" AND pch$ <= "Z") THEN
  137.             IF idx% = -1 THEN
  138.                 res$ = LEFT$(res$, LEN(res$) - 1)
  139.                 idx% = VAL(pch$): tok% = -1
  140.                 IF idx% = 0 AND pch$ <> "0" THEN idx% = ASC(pch$) - 55
  141.             ELSE
  142.                 res$ = "ERROR: Unexpected { in fmt$ at position" + STR$(cpos&) + " !!"
  143.                 IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  144.                 EXIT FOR
  145.             END IF
  146.         ELSE
  147.             res$ = "ERROR: Missing index before { in fmt$ at position" + STR$(cpos&) + " !!"
  148.             IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  149.             EXIT FOR
  150.         END IF
  151.     ELSEIF cch$ = "}" AND lit% = 0 THEN 'end of formatting token
  152.         IF idx% >= 0 THEN
  153.             GOSUB doArgFormat: res$ = res$ + tmp$
  154.             idx% = -1: ft$ = "": tok% = 0
  155.         ELSE
  156.             res$ = "ERROR: Unexpected } in fmt$ at position" + STR$(cpos&) + " !!"
  157.             IF UCASE$(errMsg) = "OFF" THEN res$ = ""
  158.             EXIT FOR
  159.         END IF
  160.     ELSE 'take literal char/symbol
  161.         IF lit% AND INSTR("\{}", cch$) = 0 THEN cch$ = "_" + cch$
  162.         IF tok% THEN ft$ = ft$ + cch$: ELSE res$ = res$ + cch$
  163.         lit% = 0
  164.     END IF
  165. NEXT cpos&
  166. '--- cleanup & set result ---
  167. ERASE argArr$
  168. _SOURCE shan&: _DEST dhan&: _FREEIMAGE than&
  169. IndexFormat$ = res$
  170. '-----------------------------
  171. doArgFormat:
  172. CLS: tmp$ = ""
  173. IF ft$ = "" THEN RETURN
  174.     CASE "!", "&", "\": PRINT USING ft$; argArr$(idx%); 'string
  175.     CASE "B", "H", "O" 'bin/hex/oct
  176.         typ$ = LEFT$(ft$, 1): tyl% = VAL(MID$(ft$, 2))
  177.         SELECT CASE typ$
  178.             CASE "B", "b": GOSUB doBinString
  179.             CASE "H", "h"
  180.                 tmp$ = HEX$(VAL(argArr$(idx%)))
  181.                 IF typ$ = "H" THEN tmp$ = UCASE$(tmp$): ELSE tmp$ = LCASE$(tmp$)
  182.             CASE "O", "o": tmp$ = OCT$(VAL(argArr$(idx%)))
  183.         END SELECT
  184.         IF tyl% > 0 THEN
  185.             IF LEN(tmp$) <= tyl% THEN
  186.                 tmp$ = RIGHT$(STRING$(tyl%, "0") + tmp$, tyl%)
  187.             ELSE
  188.                 tmp$ = "%" + tmp$
  189.             END IF
  190.         END IF
  191.         RETURN
  192.     CASE ELSE: PRINT USING ft$; VAL(argArr$(idx%)); 'number
  193. FOR i% = 1 TO POS(0) - 1
  194.     tmp$ = tmp$ + CHR$(SCREEN(1, i%))
  195. NEXT i%
  196. '-----------------------------
  197. doBinString:
  198. value&& = VAL(argArr$(idx%))
  199. temp~&& = value&&
  200. tmp$ = STRING$(64, "0"): charPos% = 64: highPos% = 64
  201.     IF (temp~&& AND 1) THEN MID$(tmp$, charPos%, 1) = "1": highPos% = charPos%
  202.     charPos% = charPos% - 1: temp~&& = temp~&& \ 2
  203. LOOP UNTIL temp~&& = 0
  204. IF value&& < 0 THEN
  205.     IF -value&& < &H0080000000~&& THEN highPos% = 33
  206.     IF -value&& < &H0000008000~&& THEN highPos% = 49
  207.     IF -value&& < &H0000000080~&& THEN highPos% = 57
  208. tmp$ = MID$(tmp$, highPos%)
  209.  
  210.  
  211.  

Edit  2020-09-10 Update: New HTML Description from RhoSigma (see attached)

« Last Edit: September 11, 2020, 09:13:48 AM by bplus »