_TITLE "FORMAT$ Wrapper Test - for quick number format" 'b+ 2020-08-24 '2020-08-27 revise FORMAT$ to take arguments for digits left and right of decimal
'let's see
FOR i
= 1 TO 10 'want to see if we can make pretty column of numbers ' build random number type single
PRINT "12345678901234567890" ' ruler PRINT FORMAT$
(12, 4, a
), "The number was:"; a
PRINT:
PRINT "Press any to continue... esc for one last test" PRINT "n = 111987654321.123456789" PRINT "PRINT FORMAT$(15, 8, n)" n = 111987654321.123456789
PRINT "n = "; FORMAT$
(15, 8, n
) PRINT "Yes, 16 digits is maximum you can keep."
'Notes on PRINT USING from wiki:
'ref http://qb64.org/wiki/PRINT_USING
' & Prints an entire string value. STRING length should be limited as template width will vary.
' \ \ Denotes the start and end point of a fixed string area with spaces between(LEN = spaces + 2).
' ! Prints only the leading character of a string value. Exclamation points require underscore prefix.
' # Denotes a numerical digit. An appropriate number of digits should be used for values received.
' ^^^^ After # digits prints numerical value in exponential E+xx format. Use ^^^^^ for E+xxx values.*
' . Period sets a number's decimal point position. Digits following determine rounded value accuracy.
' ,. Comma to left of decimal point, prints a comma every 3 used # digit places left of the decimal point.
' + Plus sign denotes the position of the number's sign. + or - will be displayed.
' - Minus sign (dash) placed after the number, displays only a negative value's sign.
' $$ Prints a dollar sign immediately before the highest non-zero # digit position of the numerical value.
' ** Prints an asterisk in any leading empty spaces of a numerical value. Adds 2 extra digit positions.
' **$ Combines ** and $$. Negative values will display minus sign to left of $.
' _ Underscore preceding a format symbol prints those symbols as literal string characters.
'quick wrapper function intended for numbers
FORMAT$
= IndexFormat$
("0{" + STRING$(maxDigitsLeft
, "#") + ",." + STRING$(digitsRight
, "#") + "}", _TRIM$(STR$(number
)), "|") '3rd argument is dummy
'-------------------
'--- IndexFormat --- 2020-08-27
'-------------------
' This function will perform PRINT USING like formatting and return the
' formatted output string, so you can either store it in a variable or
' print it directly. However, compared to the PRINT USING conventions,
' this function has several extensions. The first and most important is
' argument position specification (indexing). That is, each formatting
' token used in the format template knows to which argument it belongs,
' hence the order of the various format tokens can be changed without
' the need of reordering the provided arguments too. Second extension,
' with indexing all arguments can be easily reused, just by using the
' same index number for multiple format tokens. It is not required to
' pass the reused arguments multiple times to the function. Indexing is
' done really simple in the form IS{PUFS}, where IS = index specifier
' (0-9/A-Z, hence 36 args max.) and PUFS = PRINT USING format symbols.
'
' Have a look on the following examples:
' --------------------------------------
' Assuming head = 1, hands = 2 and fingers = 10,
' PRINT USING "## head, ## hands and ## fingers"; head, hands, fingers
' PRINT USING "## fingers, ## head and ## hands"; head, hands, fingers
' PRINT IndexFormat$("2{##} fingers, 0{##} head and 1{##} hands",_
' STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
'
' These three statements would produce the following output:
' " 1 head, 2 hands and 10 fingers" (args order matches template)
' " 1 fingers, 2 head and 10 hands" (args order wrong for template)
' "10 fingers, 1 head and 2 hands" (indexed, tokens pick right args)
'
' Reuse any arguments without providing them multiple times:
' ----------------------------------------------------------
' PRINT IndexFormat$("0{##} head, 1{##} hands and 2{##} fingers, " +_
' "also 1{##} feet and 2{##} toes",_
' STR$(head) + "|" + STR$(hands) + "|" + STR$(fingers), "|")
'
' This would produce the following output:
' " 1 head, 2 hands and 10 fingers, also 2 feet and 10 toes"
'
' Another extension are three new PUFS to format numbers into binary,
' hexadecimal or octal notation, optional with a given field length.
' The symbols are B/b, H/h and O/o respectively. For hexadecimal the
' case of the symbol determines whether to use hex letters A-F or a-f.
' The optional field length directly follows the symbol given as decimal
' number (eg. {H8} = hex uppercase 8 digits, {B16} = binary 16 digits).
' If length is used, then the field is filled with leading zeros. If
' the field is to short, then it is extended to the minimum required
' for the number and prefixed with a % as usual. If length is omitted,
' then the output length is variable just like for strings (& symbol),
' usually the minimum required number of digits for the given number.
'
' To make writing the format templates somewhat more convenient, there
' is one final extension, which allows the use of escape sequences as
' known from C/C++ and other programming languages.
'
' Using escape sequences within format templates:
' -----------------------------------------------
' fmt$ = "Column-1" + CHR$(9) + "Column-2" (bulky way adding CHR$(9))
' fmt$ = "Column-1\tColumn-2" (easy with \t sequence)
' fmt$ = "This is a \x22quoted\x22 section." (quotation with \x22)
'
' Unfortunately we can't use \" to insert a quotation mark, as the QB64
' compiler would see the " used in the sequence as the regular end of
' the template string. However, as shown in the last example we can use
' the octal or hex representations \042 or \x22 of the quotation mark.
'
' The following sequences are available for use with this function:
' -----------------------------------------------------------------
' \a = CHR$(7) ' audio bell \b = CHR$(8) ' backspace
' \t = CHR$(9) ' tabulator \n = CHR$(10) 'line feed
' \v = CHR$(11) 'vertical tabulator \f = CHR$(12) 'form feed
' \r = CHR$(13) 'carriage return \e = CHR$(27) 'escape
' \nnn = octal ASCII code ( 3 digits, eg. \042)
' \xnn = hex ASCII code (x + 2 digits, eg. \x3F)
'
'----------
' SYNTAX:
' formatted$ = IndexFormat$ (fmt$, arg$, sep$)
'
'----------
' INPUTS:
' --- fmt$ ---
' The format template as usual for PRINT USING, but all format tokens
' must be indexed, ie. enclosed in curly brackets with a leading one
' digit index number/letter, which designates the argument to use.
' All argument indices may be reused as often as you want. Missing
' arguments will be processed as empty or zero, no error will occur.
' You may also use C/C++ style escape sequences. Write _{, _} and _\
' to get literal curly brackets or a backslash in the output and __
' to get an underscore. All other format symbols doesn't need to be
' underscored anymore to make them literals. Inside the {} tokens no
' underscores are allowed at all, these can contain the PRINT USING
' or the new B/H/O format symbols only.
' --- arg$ ---
' This is a concatenated string of all arguments separated by a user
' chosen char. Use STR$(numVar) to add in a numeric variable. It won't
' hurt, if there are more arguments than used in the format template.
' --- sep$ ---
' The char used to separate the arguments in the concatenated argument
' string. Choose a char, which is not used in the arguments itself.
' If you need all printable chars, then CHR$(0) might be suitable.
' This is required, even if you pass a single argument only.
'
'----------
' RESULT:
' --- formatted$ ---
' Regularly the formatted string on success. If there's a problem with
' your provided format template, then a appropriate error message will
' be returned by default. If you don't like that and rather want the
' function to return an empty string on failure, then simply adjust
' the "CONST errMsg" line right below to switch the error behavior.
'---------------------------------------------------------------------
FUNCTION IndexFormat$
(fmt$
, arg$
, sep$
) '--- set your desired error behavior ---
CONST errMsg
= "ON" 'ON = return error messages, OFF = return empty '--- option _explicit requirements ---
DIM args$
, shan&
, dhan&
, than&
, i%
, spos&
, res$
, idx%
DIM ft$
, tok%
, lit%
, cpos&
, cch$
, nch$
, ech$
, pch$
, tmp$
DIM typ$
, tyl%
, value&&
, temp~&&
, charPos%
, highPos%
'--- init ---
args$ = arg$ 'avoid side effects
'--- parse arguments ---
spos&
= INSTR(args$
, sep$
) argArr$
(i%
) = LEFT$(args$
, spos&
- 1) args$
= MID$(args$
, spos&
+ 1) '--- process format template ---
res$ = "": idx% = -1: ft$ = "": tok% = 0: lit% = 0
cch$
= MID$(fmt$
, cpos&
, 1) IF cch$
= "_" AND lit%
= 0 THEN 'next symbol is literal lit% = -1
res$
= "ERROR: No _ allowed inside {} in fmt$ at position" + STR$(cpos&
) + " !!" nch$
= MID$(fmt$
, cpos&
+ 1, 1) CASE "V": ech$
= CHR$(11) 'vertical tabulator CASE "R": ech$
= CHR$(13) 'carriage return CASE "0", "1", "2", "3" ' octal ASCII (3 digits) cpos& = cpos& + 2
CASE "X" ' hex ASCII (x + 2 digits) cpos& = cpos& + 2
res$ = res$ + ech$
cpos& = cpos& + 1
IF (pch$
>= "0" AND pch$
<= "9") OR (pch$
>= "A" AND pch$
<= "Z") THEN idx%
= VAL(pch$
): tok%
= -1 res$
= "ERROR: Unexpected { in fmt$ at position" + STR$(cpos&
) + " !!" res$
= "ERROR: Missing index before { in fmt$ at position" + STR$(cpos&
) + " !!" GOSUB doArgFormat: res$
= res$
+ tmp$
idx% = -1: ft$ = "": tok% = 0
res$
= "ERROR: Unexpected } in fmt$ at position" + STR$(cpos&
) + " !!" ELSE 'take literal char/symbol IF tok%
THEN ft$
= ft$
+ cch$:
ELSE res$
= res$
+ cch$
lit% = 0
'--- cleanup & set result ---
IndexFormat$ = res$
'-----------------------------
doArgFormat:
CASE "B", "H", "O" 'bin/hex/oct tmp$ = "%" + tmp$
'-----------------------------
doBinString:
value&&
= VAL(argArr$
(idx%
)) temp~&& = value&&
tmp$
= STRING$(64, "0"): charPos%
= 64: highPos%
= 64 IF (temp~&&
AND 1) THEN MID$(tmp$
, charPos%
, 1) = "1": highPos%
= charPos%
charPos% = charPos% - 1: temp~&& = temp~&& \ 2
IF -value&&
< &H0080000000~&&
THEN highPos%
= 33 IF -value&&
< &H0000008000~&&
THEN highPos%
= 49 IF -value&&
< &H0000000080~&&
THEN highPos%
= 57 tmp$
= MID$(tmp$
, highPos%
)