#lang scribble/manual @(require "fmt.rkt" scribble/eval (for-label "fmt.rkt" racket)) @title{A simple formatter for Racket} @author{Jacob J. A. Koot} @(defmodule "fmt.rkt") @section{Rationale} Simple formatting tools can be useful when readability of output for the human eye is of some importance, but not to the extent that a highly finished presentation is required. Examples of simple formatters for @(hyperlink #:underline? #t "http://racket-lang.org/" "Racket") are the various versions of procedure @racket[format]. They are handy tools, but in some cases may not provide enough functionality. Which functions a simple formatter should provide and which shape it should have is a matter of personal taste, I think. In this document I show a simple formatter that more or less satisfies my own taste. It combines some of the powerful features of the format-statement of @(hyperlink #:underline? #t "http://en.wikipedia.org/wiki/Fortran" "Fortran") with the flexibility of Racket’s generic procedures @racket[write] and @racket[display]. The principles are simple enough to get started almost immediately. Nevertheless tools for padding, tabulation, several numerical formats, literals, iteration and compound instructions are included. Field widths, tabulator positions and repetition counts can be constants within the format, but can also be obtained from the data. @bold{Warning}: padding and tabulation are implemented by insertion of spaces and therefore require a fixed width character font, such as @(hyperlink #:underline? #t "http://en.wikipedia.org/wiki/Courier_(typeface)" "Courier"), in order to function properly. @section{Format-procedures} Formats can be regarded as procedures that transform data from one representation into another one. Procedure @racket[fmt] is based on this idea. It translates strings of format-instructions into a format-procedure, that is, a Racket procedure that accepts data and returns the formatted external representation of the data as a string or sends it through an output-port. The amount of output that can be produced by a single format-procedure call may be subjected to a platform dependent limit, because the produced output is gathered in an output-string before being committed. If during formatting an error is detected, an exception is raised and no output is produced at all. @section{Procedure fmt} @defproc[(fmt (format (or/c string? fmt?)) ... (port (or/c output-port? 'string 'current 'argument) 'string)) fmt?] Procedure @racket[fmt] returns a format-procedure. The port-argument may be placed at arbitrary position among the format-arguments. The symbols @racket['string], @racket['current] and @racket['argument] can be abbreviated as @racket['str], @racket['cur] and @racket['arg]. A format-argument is either a format-string or a format-procedure made by procedure @racket[fmt]. All format-strings are parsed, checked for correctness and translated to subprocedures of the format-procedure to be produced. If no format-argument is given, @racket[""] is assumed. Adjacent format-strings are parsed as though concatenated to form one single string with commas inserted between the individual strings. The commas are separators. Each argument consisting of a format-procedure is inserted as a compound instruction without being parsed or translated again. The ports of the inserted format-procedures are ignored. @defproc[#:kind "predicate" (fmt? (object any/c)) boolean?]{ Returns @racket[#t] if the argument is a format-procedure made by procedure @racket[fmt], else @racket[#f].} @defproc[#:kind "predicate" (fmtp? (object any/c)) boolean?]{ Returns @racket[#t] if the argument is a format-procedure made by procedure @racket[fmt] with port-argument @racket['argument] or @racket['arg], else @racket[#f].} @section{Format-procedure calls} When procedure @racket[fmt] was called without a @racket[port] argument or with an output-port or symbol @racket['string] or @racket['current] for its @racket[port] argument, the returned format-procedure, say @code{fmt-proc-1}, can be called as follows: @(define fmt-proc-1 void) @defproc[#:kind "format-procedure" (fmt-proc-1 (datum any/c) ...) (or/c string? void?)] When procedure @racket[fmt] was called with symbol @racket['argument] for its @racket[port] argument, the returned format-procedure, say @code{fmt-proc-2}, can be called as follows: @(define fmt-proc-2 void) @defproc[#:kind "format-procedure" (fmt-proc-2 (port (or/c output-port? 'string 'current)) (datum any/c) ...) (or/c void? string?)] In both cases the format-procedure formats the data according to the the format-arguments from which it was built and the formatted data are sent through an output-port or returned as a string. Examples: @margin-note{Instruction @seclink["I" "I5"] takes an integer number and displays it right justified in a field of 5 characters. In the results `◦´ is used to show spaces.} @code{((fmt "I5") 12)} → @code{"◦◦◦12"} @code{((fmt "I5" 'string) 12)} → @code{"◦◦◦12"} @code{((fmt "I5" 'current) 12)} → void, displays @code["◦◦◦12"] @code{((fmt "I5" (current-output-port)) 12)} → void, displays @code["◦◦◦12"] @code{((fmt "I5" 'argument) 'string 12)} → @code{"◦◦◦12"} @code{((fmt "I5" 'argument) 'current 12)} → void, displays @code["◦◦◦12"] @code{((fmt "I5" 'argument) (current-output-port) 12)} → void, displays @code["◦◦◦12"] Forgetting the port-argument when calling a format-procedure made with port-argument @racket['argument] raises an error: @interaction[ (require "fmt.rkt") (define my-fmt (fmt "I5" 'argument)) (my-fmt 12)] @section{Format-strings} A format-string describes a format-procedure in its own simple language. The format-instructions are non verbose. Each elementary instruction consists of one single character, possibly preceded by a repetition count and followed by one or more numeric arguments, such as a field width or tabulator position. A format-string can include literal data, for example for headers. There is no distinction between lower case and capital letters, except within literal data. Separators, id est white space and commas, are irrelevant except in the following cases: @itemlist[(list @item{Within a literal, separators are part of the literal.} @item{No separator must appear within a numeric argument or iteration count.} @item{Some instructions may be followed by one or more numerical arguments. These arguments may be omitted starting from the last one. However, when omitting one or more arguments where the next instruction starts with a token that can be interpreted as a numerical argument, a comma is required as separator, optionally preceded and/or followed by other separators. Hence, no comma must appear before any numerical argument belonging to the preceding instruction. White space is required between two numerical arguments if the first character of the second one could be parsed as belonging to the previous one.} @item{Where a literal is followed by a literal beginning with a single quote a separator is required. The reason is that the start and end of a literal are marked by a single quote and that within a literal two immediately adjacent single quotes are read as one single quote belonging to the literal.})] @section{Format-instructions} @margin-note{See the @seclink["synopsis" "synopsis"] for a list of all format-instructions.} A format-procedure is given data to be formatted. A format-instruction that consumes a datum, removes it from the list of data in order that the next data consuming instruction gets the next datum. An error is reported if an attempt is made to execute a data consuming instruction after all data already have been consumed or when unconsumed data remain after completion of the format-procedure. There are instructions that add data to the list of remaining data, possibly after consuming data. The added data always are the first to be consumed next. For example, instruction @seclink["Y" "Y"] consumes a datum, which must be a number, and adds the real and the imaginary part of the number as two separate real numbers in front of the remaining data. Subsequent format-instructions can treat the real and imaginary part separately. @subsection{Instruction arguments} In the description of the instructions, ξ represents an argument consisting of an instruction. ν, μ and ε represent numerical arguments such as a field width or a tabulator position. Omitted numerical arguments are zero. A repetition count has the same form as a numerical argument. Numerical arguments and repetition counts can have one of the following three forms: @tabular[#:sep @hspace[3] #:style 'top (list (list @verbatim["δ...+"] "A sequence of one or more decimal figures.") (list "" "") (list @verbatim["δ...‹period›\n"] "A sequence of zero or more decimal figures immediately followed by a period. A period without immediately preceding decimal figure is\ninterpreted as zero.") (list "" "") (list @verbatim["#\n"] "Consumes a datum, which must be a natural number. This number is used as numerical argument or repetition count. In data consuming instructions the hash (or sharp) signs consume their numbers first."))] Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "I5") 12)} → @code{"◦◦◦12"} integer format, field width 5. @code{((fmt "I5.3") 12)} → @code{"◦◦012"} integer format, field width 5, at least 3 decimal figures. @code{((fmt "I##") 5 3 12)} → @code{"◦◦012"} idem taking the arguments from the data. @subsection{Elementary format-instructions} @margin-note{See the Racket documentation: @hyperlink["http://docs.racket-lang.org/reference/printing.html"]{The Printer} for the distinction between @racket[display], @racket[write] and @racket[print]} @subsubsub*section[#:tag "D" "D"] Consumes one datum and @racket[display]s it according to the current @seclink["Padding" "padding"] mode. If the datum is a string and padding is switched on, heading and trailing spaces are removed before the string is padded. No spaces are removed from or added to strings that are non trivial parts of the datum, for example in case the datum is a list of strings. @subsubsub*section[#:tag "W" "W"] Consumes one datum and @racket[write]s it according to the current padding mode. @subsubsub*section[#:tag "P" "P"] Consumes one datum and @racket[print]s it according to the current padding mode. @subsubsub*section[#:tag "X" "X"] Displays one space. @subsubsub*section[#:tag "/" "/"] Executes a newline instruction and marks the start of this line. See @seclink["Tabulation" "tabulation"]. @subsubsub*section[#:tag "|" "|"] Executes a newline instruction only if not at the start of the current line. Examples: @code{((fmt 'current "DXDXD") "Jacob" 3 #\x)} → void, displays @code{Jacob 3 x} @code{((fmt 'current "WXWXW") "Jacob" 3 #\x)} → void, displays @code{"Jacob" 3 #\x} @code{((fmt 'current "D") (list "Jacob" 3 #\x))} → void, displays @code{(Jacob 3 x)} @code{((fmt 'current "W") (list "Jacob" 3 #\x))} → void, displays @code{("Jacob" 3 #\x)} @subsection{Padding} Padding applies to @seclink["literal" "literal data"] and the instructions @seclink["D" "D"] @seclink["W" "W"], @seclink["P" "P"], @seclink["B" "B"], @seclink["O" "O"] and @seclink["H" "H"]. When padding is switched on, these instructions add heading and/or trailing spaces if otherwise fewer characters would be generated than indicated by the field width. Instruction @seclink["D" "D"] first removes heading and trailing spaces when padding a string. The same holds when padding a literal datum. Output that does not fit within the field width is not truncated. Initially padding is switched off. When a format-procedure is called from another format-procedure, the former inherits the padding mode from the latter. If the called procedure alters the padding mode, the alteration remains effective after return. Instruction @seclink["A" "A"] can be used to restore the previous padding mode. @bold{Warning}: because padding is done by insertion of spaces, it is useful only with a font of fixed character width. @subsubsub*section[#:tag "N" "N"] Switches padding off. No spaces are removed or added. @subsubsub*section[#:tag "L" "L"@larger{ν}] For left alignment in fields of ν characters. @subsubsub*section[#:tag "R" "R"@larger{ν}] For right alignment in fields of ν characters. @subsubsub*section[#:tag "C" "C"@larger{ν}] For centred alignment in fields of ν characters. When needed, spaces are added to the left and to the right in order to match the field width. If the number of spaces to be added is even, say @italic{2k}, then @italic{k} spaces are added both at the left and at the right. If the number of spaces to be added is odd, say @italic{2k+1}, then @italic{k+1} spaces are added to the left and @italic{k} spaces to the right. @subsubsub*section[#:tag "A" "A"@larger{ξ}] Memorizes the current padding mode and field width, executes instruction ξ and upon completion restores the memorized padding mode and field width. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "L5*D") 1 2 3)} → @code{"1◦◦◦◦2◦◦◦◦3◦◦◦◦"} @code{((fmt "R5*D") 1 2 3)} → @code{"◦◦◦◦1◦◦◦◦2◦◦◦◦3"} @code{((fmt "C5*D") 1 2 3)} → @code{"◦◦1◦◦◦◦2◦◦◦◦3◦◦"} @code{((fmt "N D") "◦◦Jacob◦◦")} → @code{"◦◦Jacob◦◦"} ; no spaces removed, nor added. @code{((fmt "L0 D") "◦◦Jacob◦◦")} → @code{"Jacob"} ; spaces removed, no spaces added. @code{((fmt "L8 D") "◦◦Jacob◦◦")} → @code{"Jacob◦◦◦"} ; spaces first removed, then added. @subsection[#:tag "literal" "Literal data"] @subsubsub*section[#:tag "simple-literal" @larger["'κ ...'"]] Each κ is an arbitrary character, except that a single quote must be written as two immediately adjacent single quotes. The string "κ..."} is displayed according to the current padding mode. @subsubsub*section[#:tag "compound-literal" @larger["^ 'κ ...'"]] κ... has the same form as above. The string must contain zero or more symbolic expressions. They are not evaluated. They are read at parsing time and added to the list of remaining data during execution of the format-procedure. The leftmost symbolic expression becomes the first next datum. In the symbolic expressions each single quote must be written as two immediately adjacent single quotes. Because within a literal, two adjacent single quotes are read as one single quote being part of the literal, a separator is required between two adjacent literals when the second one is of the @seclink["simple-literal" "first form"]. @bold{Warning}: in the arguments of procedure @racket[fmt], 'κ...' must not be split over two or more format-strings. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "R10'1Article','Price'"))} → @code{"◦◦◦Article◦◦◦◦◦Price"} @code{((fmt "^'Article◦Price'R10◦2D"))} → @code{"◦◦◦Article◦◦◦◦◦Price"} @interaction[ (require "fmt.rkt") (fmt "'Article" "Price'")] @subsection{Numerical formats} The numerical format-instructions @code{I}, @code{F} and @code{E} have their own padding, independent from the padding mode described elsewhere in this document. @subsubsub*section[#:tag "I" "I"@larger{νμ}] Consumes and displays a real number. First the sign of the datum is determined. Subsequently the datum is rounded to an exact integer number. If rounding produces zero, the sign is retained. If a sign is to be written, it is placed immediately in front of the first decimal figure. Leading spaces are added if otherwise less than ν characters would be produced. In all cases the result contains at least μ decimal figures, padding with heading zeros when necessary, or at least one decimal figure if μ is zero. The exceptional numbers @code{±inf.0} and @code{±nan.0} are treated specially. They are right justified in a field of at least ν characters. Instruction @code{I} is particularly useful for integer numbers, but nevertheless accepts any real number. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "*I3") 2 3.4 5.6)} → @code{"◦◦2◦◦3◦◦6"} @code{((fmt "*I3.2") 2 3.4 5.6)} → @code{"◦02◦03◦06"} @code{((fmt "I") 0.1)} → @code{"0"} @code{((fmt "I") -0.1)} → @code{"-0"} @code{((fmt "I") 1.0e-100000)} → @code{"0"} @code{((fmt "I") -1.0e-100000)} → @code{"-0"} @code{((fmt "I") 1.0e1000000)} → @code{"+inf.0"} @code{((fmt "I") -1.0e1000000)} → @code{"-inf.0"} @code{((fmt "I") (/ 0.0 0.0))} → @code{"+nan.0"} @code{(string-length ((fmt "I") #e1e100000))} → @code{100001} @subsubsub*section[#:tag "F" "F"@larger{νμ}] Consumes and displays a real number in decimal expansion: leading spaces, [sign], integer part, period, fraction of exactly μ decimal figures. The datum is rounded such as to fit the width of the fraction. If rounding yields zero, the sign is retained. The result has at least one decimal figure before the period. Leading spaces are added if otherwise less than ν characters would be produced. The exceptional numbers @code{±inf.0} and @code{±nan.0} are treated specially. They are right justified in a field of at least ν characters. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "*F3 ") 2 3.4 5.6)} → @code{"◦2.◦3.◦6."} @code{((fmt "*F5.2") 2 3.4 5.6)} → @code{"◦2.00◦3.40◦5.60"} @code{((fmt "F.4") 2/3)} → @code{"0.6667"} @code{((fmt "D") 2/3)} → @code{"2/3"} @code{((fmt "F.2") 1.0e1000000)} → @code{"+inf.0"} @code{((fmt "F.2") -1.0e1000000)} → @code{"-inf.0"} @code{((fmt "F.2") -1.0e-100000)} → @code{"-0.00"} @subsubsub*section[#:tag "E" "E"@larger{νμε}] Consumes and displays a real number in scientific notation: leading blanks, [sign], one decimal figure, period, exactly μ decimal figures, letter e, sign of exponent, ε or more decimal figures of exponent. If the number is not zero, the output is normalized such that the decimal figure before the decimal point is not zero. If the number is zero, all decimal figures are zero. Leading spaces are added if otherwise less than ν characters would be produced. The exceptional numbers @code{±inf.0} and @code{±nan.0} are treated specially. They are right justified in a field of at least ν characters. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "*E10.3.2") 2/3 2.3e-2)} → @code{"◦6.667e-01◦2.300e-02"} @code{((fmt "E.5") 2/3)} → @code{"6.66667e-1"} @code{((fmt "E15.5.4") 2/3)} → @code{"◦◦6.66667e-0001"} @code{((fmt "EXE") #e-1e1000000 -1.0e1000000)} → @code{"-1.e+100000◦-inf.0"} @code{((fmt "EXE") #e-1e-100000 -1.0e-100000)} → @code{"-1.e-100000◦-0.e+0"} @subsubsub*section[#:tag "B" "B"] Displays a real number in binary notation. @subsubsub*section[#:tag "O" "O"] Displays a real number in octal notation. @subsubsub*section[#:tag "H" "H"] Displays a real number in hexadecimal notation. Instructions @code{B}, @code{O} and @code{H} convert the number into an exact one and use @racket[number->string] to convert the absolute value and display the result according to the current padding and sign mode. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "H") 20/31)} → @code{"14/1f"} @code{((fmt "D") 20/31)} → @code{"20/31"} @code{((fmt "H") -2.71)} → @code{"-ad70a3d70a3d7/4000000000000"} @code{((fmt "H") 2.71e-2)} → @code{"6f0068db8bac7/100000000000000"} @code{((fmt "H") #e2.71e-2)} → @code{"10f/2710"} ; 271 in the denominator is a coincidence. @code{((fmt "R8 H") 2.71e-200000)} → @code{"◦◦◦◦◦◦◦0"} @code{((fmt "R8 H") -2.71e-200000)} → @code{"◦◦◦◦◦◦-0"} @code{((fmt "R8 H") 2.71e2000000)} → @code{"◦◦+inf.0"} @code{((fmt "H") 0)} → @code{"0"} @subsection{Sign mode} The sign mode is relevant for the numerical instructions @seclink["I" "I"], @seclink["F" "F"], @seclink["E" "E"], @seclink["B" "B"], @seclink["O" "O"] and @seclink["H" "H"]. When sign mode is off, positive numbers get no sign. When sign mode is on, positive numbers, exact zero and @code{+0.0} get a plus sign. Negative numbers always get a minus sign, @code{-0.0} included. Instructions @seclink["I" "I"], @seclink["F" "F"] and @seclink["E" "E"] may round the number such as to fit the width of the fraction. If rounding a negative number yields zero, the minus sign is retained. When a format-procedure is called from another format-procedure, the former inherits the sign mode from the latter. If the called procedure alters the sign mode, this mode remains effective after return. Instruction @seclink["$" "$"] can be used to restore the previous sign mode. @subsubsub*section[#:tag "+" @larger["+"]] Switches sign mode on. This is relevant for the numerical format-instructions only. @subsubsub*section[#:tag "-" @larger["-"]] Switches sign mode off. This is relevant for the numerical format-instructions only. @subsubsub*section[#:tag "$" "$"@larger{ξ}] Memorizes the current sign mode, executes instruction ξ and upon completion restores the memorized sign mode. Example: @margin-note{`◦´ is used to show spaces} @code{((fmt "+ F6.2 $(-F6.2) F6.2") 3.4 3.4 3.4)} → @code{"◦+3.40◦◦3.40◦+3.40"} @subsection{Tabulation} Tabulator instructions reposition the write head within or beyond the end of the current line. The first character of the current line has index 0. Initially the current line starts at the very beginning of the output to be produced. The newline instructions / and | shift the start of the current line to the start of the new line. New lines made in any other way (for example when part of a literal datum) do not reposition the start of the current line. They are considered to be part of the current line. If the new position is beyond the end of the current line, spaces are added, but no existing output is erased. Placing the write head before the end of the current line does not erase output, but allows subsequent output to replace previous output. The instructions are effective even if the output device does not allow reposition of the write head. The tabulator is useful only with fixed width character fonts, such a Courier font. @subsubsub*section[#:tag "T" "T"@larger{ν}] Places the write head at position ν of the current line. @subsubsub*section[#:tag ">" ">"@larger{ν}] Positions the write head forward relative to the current position. @subsubsub*section[#:tag "<" "<"@larger{ν}] Positions the write head backwards relative to the current position. An error is signalled when an attempt is made to position the write head before the start of the current line. @subsubsub*section[#:tag "&" "&"] Positions the write head at the end of current line. @subsubsub*section[#:tag "@" "@"@larger{ξ}] Memorizes the position of the start of the current line, executes instruction ξ and upon completion restores the memorized position. @bold{Warning}: after restoring, new lines produced by ξ become part of the original current line. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "T10 D T6 D T2 D &R4D") 1 2 3 4)} → @code{"◦◦3◦◦◦2◦◦◦1◦◦◦4"} @code{((fmt "*(T#D)") 1 1 4 4 3 3 5 5 2 2 6 6 7 7 0 0)} → @code{"01234567"} @subsection[#:tag "condition" "Conditional instructions"] @subsubsub*section[#:tag "!" "!"@larger{ξ}] Executes instruction ξ only if there are more data. @subsubsub*section[#:tag "?" "?"@larger{ξ}] Executes instruction ξ only if there are no more data. @subsubsub*section[#:tag "Q" "Q"@larger{ξξ}] Inspects the next datum without consuming it. If it is true, the first ξ is executed. If it is false, the second ξ is executed. An error is signalled if there are no more data. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "!(*(D!X)/)") 1 2 3 4)} → @code{"1◦2◦3◦4\n"} @code{((fmt "!(*(D!X)/)"))} → @code{""} @code{((fmt "Q'True','False'S") #t)} → @code{"True"} ; Instruction @code{S} removes @code{#t} from the data. @code{((fmt "*Q(DX)S") 'a #f 'b)} → @code{"a◦b◦"} ; Displays true data only. @subsection[#:tag "iteration"]{Iterations} @subsubsub*section[#:tag "*ξ" "*"@larger{ξ}] Repeated execution of ξ until no data remain. @subsubsub*section[#:tag "νξ" @larger{νξ}] Instruction ξ is executed ν times. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "R3 4D") 1 2 3 4)} → @code{"◦◦1◦◦2◦◦3◦◦4"} @code{((fmt "R3 *D") 1 2 3 4)} → @code{"◦◦1◦◦2◦◦3◦◦4"} @code{((fmt "R# #D") 3 4 1 2 3 4)} → @code{"◦◦1◦◦2◦◦3◦◦4"} @subsection[#:tag "compound"]{Compound instructions} @subsubsub*section[#:tag "simple-compound" "("@larger{ξ}" ...)"] Compound instruction. Useful for @seclink["condition" "conditions"] and @seclink["iteration" "iterations"]. @subsubsub*section[#:tag "special-compound" "["@larger{ξ}" ...]"] Special compound instruction. The output of the instructions is gathered in a string which after completion of the compound instruction is added to the remaining data and becomes the first next datum. Each special compound instruction has its own offset for the tabulator. The square brackets are part of the instruction. They do not indicate that ξ... is optional. In fact the ellipsis makes ξ... optional. [] produces an empty string. @bold{Warning}: new lines produced by ξ... become part of the original current line. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "!(*(D!X)/)") 1 2 3 4)} → @code{"1◦2◦3◦4\n"} @code{((fmt "L3 [*D] C20 D") 1 2 3 4)} → @code{"◦◦◦◦◦1◦◦2◦◦3◦◦4◦◦◦◦◦"} @code{((fmt "['123'/'456'] T1 D T0 '0'"))} → @code{"0123\n456"} @subsection{Miscellaneous instructions} @subsubsub*section[#:tag "M" "M"@larger{ξ}] Memorizes the current padding mode, field width, tabulator offset and sign mode, executes the instruction and restores the memorized state. @bold{Warning}: new lines produced by ξ become part of the original current line. @subsubsub*section[#:tag ":" ":"] Exits from @seclink["compound" "a compound instruction"] or from a format-procedure or format-string called with instruction @seclink["K" "K"]. At top level same as instruction @bold{;}. @subsubsub*section[#:tag ";" ";"] Exits from the top level format-procedure. @subsubsub*section[#:tag "S" "S"] Skips one datum. @subsubsub*section[#:tag "~" @larger{~}] Positions the write head at the end of the current line and writes all remaining data separated by spaces and terminated by a newline. Same as @code{"!(&n*(w!x)/)"}. Usually it is wise to write @code{"x~"} or @code{"|~"} in order to separate the remaining output from output already produced. @subsubsub*section[#:tag "G" "G"] Consumes a datum which must be a natural number or @code{#f}. It is supposed to be a time measured in seconds from the platform specific starting time. @code{#f} is for the current time. The time is displayed as: @code["DDD,◦dd◦MMM◦yyyy◦hh:mm:ss◦±hhmm"] (exactly 31 characters and `◦´ used to show spaces)}) A limit may be imposed on the number of seconds. On many platforms the datum is restricted to a fixnum or even less. @tabular[#:sep @hspace[1] (list (list @code["DDD"] "First three letters of the name of the day of the week.") (list @code["dd"] "Two decimal figures for the number of the day of the month.") (list @code["MMM"] "First three letters of the name of the month.") (list @code["yyyy"] "Four decimal figures for the year.") (list @code["hh"] "Two decimal figures for the hour of the day on 24 hour basis.") (list @code["mm"] "Two decimal figures for the minute within the hour.") (list @code["ss"] "Two decimal figures for the second within the minute (leap second included)") (list @code["±hhmm"] "Time zone, hours and minutes, sign followed by four decimal figures."))] Examples (assuming Windows XP or Windows 7 in time zone +0100) @code{((fmt "G") 0)} → @code{"Thu,◦01◦Jan◦1970◦01:00:00◦+0100"} @code{((fmt "^'0' G"))} Same as: @code{((fmt "G") 0)} @code{((fmt "G") (sub1 (expt 2 31)))} → @code{"Tue,◦19◦Jan◦2038◦04:14:07◦+0100"} @code{((fmt "G") #f)} Same as: @code{((fmt "G") (current-seconds))} @code{((fmt "^'#f' G"))} Same as: @code{((fmt "G") #f)} @subsection{Unfolding} @subsubsub*section[#:tag "U" "U"] Unfolds the next datum. If the datum is a vector or list, the elements will be treated as separate components of data. These data are preceded by the number of elements. A structure is first converted to a vector and the latter is unfolded. If the datum is not a vector, list or structure it remains in the list of data as it is and the exact number one is consed to the data as the first next datum. Improper lists are not unfolded. @subsubsub*section[#:tag "V" "V"] Recursively unfolds the next datum. The elements will be treated as separate components of data. These data are preceded by the number of elements. @subsubsub*section[#:tag "Z" "Z"] Recursively unfolds all remaining data. The elements will be treated as separate components of data. These data are preceded by the number of elements. @subsubsub*section[#:tag "Y" "Y"] Consumes a datum, which must be a number. It is decomposed into its real and imaginary part. These are consed as two real numbers to the remaining data, the real part becoming the first next datum, the imaginary part the second one. @subsubsub*section[#:tag "%" "%"] Consumes a datum, which must be a real number. In Racket all real numbers are rational too. The number is decomposed into its numerator and denominator. These are consed as two exact integer numbers to the remaining data, the numerator becoming the first next datum, the denominator the second one. The sign is associated with the numerator, the denominator always being positive. @bold{Warning}: @code["V"] and @code["Z"] are not protected against circular structures such as a vector containing itself as an element. This warning does not apply to lists, because improper lists are not unfolded. Examples: @margin-note{`◦´ is used to show spaces} @code{((fmt "U#(DX)") '(a b c d))} → @code{"a◦b◦c◦d◦"} @code{((fmt "U*(DX)") '(a b c d))} → @code{"4◦a◦b◦c◦d◦"} @code{((fmt "YF.3+F.2'i'") -12.34+56.78i)} → @code{"-12.340+56.78i"} @code{((fmt 'current "*(%R20DN'/'L20D/)") 110/333 -1/3 0.75 -0.75 (/ 1.0 3))} displays: @verbatim{◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦110/333◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦ ◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦-1/3◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦ ◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦3/4◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦ ◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦-3/4◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦◦ ◦◦◦◦6004799503160661/18014398509481984◦◦◦} @subsection{Procedure calls} @subsubsub*section[#:tag "λ" @larger{λ}] Consumes the next datum, which must be a procedure. The procedure must accept one argument, for which it receives the list of remaining data. It must return a list. This list replaces the list of remaining data. The procedure call is wrapped in a continuation barrier in order to prohibit re-entry by means of a continuation in the body of the procedure. Allowing re-entrance could lead to unexpected results when the called procedure returns a second time after the state of the format-procedure has been altered or the format-procedure already has finished. @subsubsub*section[#:tag "K" "K"] Consumes the next datum, which must be a format-procedure or a format-string. If a format-string is given, it is checked and translated. If a format-procedure is given, its port is ignored. The instructions are executed as part of the calling format-procedure. The called format-procedure inherits the padding mode, tabulator and sign mode from the caller. If it alters the state, the alterations remain effective after return. Instructions @seclink["A" "A"], @seclink["@" "@"], @seclink["$" "$"] and @seclink["M" "M"] can be used to preserve the padding mode, tabulator offset and sign mode. @subsection{Reading from an input port} @subsubsub*section[#:tag "=" @larger{=}] Consumes one datum, which must be an input port. One datum is read from that port. If a datum has successfully been read, @code{#t} and the datum are prefixed to the list of remaining data. If an end of file was found, @code{#f} is prefixed to the list of remaining data. @subsubsub*section[#:tag "J" "J"] Consumes one datum, which must be an input port. All data are read from that port starting from its current position up to an end of file mark. The read data are prefixed to the list of remaining data. The prefixed data are preceded by an exact integer number specifying the number of read data. The end of file mark is not included in the data. Example: @margin-note{`◦´ is used to show spaces} @racketblock[ (define kopy (let ((fmt (fmt "JS~" 'port))) (lambda (p q) (fmt q p)))) (kopy (open-input-string "◦◦Copied◦◦with◦\n◦single◦◦spaces◦◦") 'string)] → @code{"Copied◦with◦single◦spaces\n"} @section{Reuse of format-procedures} @racketblock[ (define a (fmt "X'billy'XD")) (define b (fmt "X'minny'XD")) (define c (fmt "*(" a "!" b ")")) (c 1 2 3 4 5)] → @code{"◦billy◦1◦minny◦2◦billy◦3◦minny◦4◦billy◦5"} When format-procedure c is constructed, format-procedures a and b are inserted. The result is the same as with @code{"*((X'billy'XD)!(X'minny'XD))"}, but without parsing the inserted parts again. Notice that the compound instruction in the definition of c is distributed over more than one argument. Left parentheses may be balanced by format-string arguments yet to follow. @section{Elaborated examples} @subsection{Printing a bill} Procedure @code{print-bill} accepts a list of entries, each entry being a list of three arguments: the name of an article, how many pieces of this article are bought and the price per piece. The purpose of the procedure is to display a detailed bill. @racketblock[ (define print-bill (let ((line "N40('-')/") (headers "R10'article','number','price pp','total'/") (data "R10U#(USUS2D2F10.2/)") (code:comment @#,t{"US" means: unfold and skip element count}); (grand-total "R30'grand total'F10.2/")) (let ((fmt-proc (fmt "/" line headers line data line grand-total line 'current))) (lambda (table) (let* ((totals (map (λ (x) (* (cadr x) (caddr x))) table)) (grand-total (apply + totals))) (fmt-proc (map list table totals) grand-total)))))) (print-bill '((chair 4 50) (table 1 100) (pillow 4 10)))] → void, and displays: @verbatim{---------------------------------------- article number price pp total ---------------------------------------- chair 4 50.00 200.00 table 1 100.00 100.00 pillow 4 10.00 40.00 ---------------------------------------- grand total 340.00 ----------------------------------------} @subsection{Triangle of Pascal} In this example padding C is used in order to form a triangle of Pascal with its base at the bottom and all other lines centred above the bottom line. @racketblock[ (define binomials (let ((fmt-row (fmt "R5 D US C4 [*D] C45 D")) (fmt-table (fmt "/U#(D/)" 'current))) (define (make-next-row order prev-row) (list->vector (cons 1 (let loop ((j 1)) (if (> j order) '(1) (cons (+ (vector-ref prev-row (sub1 j)) (vector-ref prev-row j)) (loop (add1 j)))))))) (lambda (n) (fmt-table (let loop ((order 0) (row #1(1))) (cons (fmt-row order row) (if (>= order n) '( ) (loop (add1 order) (make-next-row order row))))))))) (binomials 9)] → void, and displays: @verbatim{ 0 1 1 1 1 2 1 2 1 3 1 3 3 1 4 1 4 6 4 1 5 1 5 10 10 5 1 6 1 6 15 20 15 6 1 7 1 7 21 35 35 21 7 1 8 1 8 28 56 70 56 28 8 1 9 1 9 36 84 126 126 84 36 9 1 } @section[#:tag "synopsis"]{Synopsis} @tabular[#:sep @hspace[3] (list (list @secref[#:underline? #f]{A} "Preserve padding") (list @secref[#:underline? #f]{B} "Binary numeric format") (list @secref[#:underline? #f]{C} "Centred padding") (list @secref[#:underline? #f]{D} "Display") (list @secref[#:underline? #f]{E} "Scientific numeric format") (list @secref[#:underline? #f]{F} "Decimal expansion numeric format") (list @secref[#:underline? #f]{G} "Date and time") (list @secref[#:underline? #f]{H} "Hexadecimal numeric format") (list @secref[#:underline? #f]{I} "Integer numeric format") (list @secref[#:underline? #f]{J} "Read up to end of file") (list @secref[#:underline? #f]{K} "Call fmt-procedure or format-string") (list @secref[#:underline? #f]{L} "Left padding") (list @secref[#:underline? #f]{M} "Preserve state (padding, sign-mode and tabulator)") (list @secref[#:underline? #f]{N} "No padding") (list @secref[#:underline? #f]{O} "Octal numeric format") (list @secref[#:underline? #f]{P} "Print") (list @secref[#:underline? #f]{Q} "Conditional") (list @secref[#:underline? #f]{R} "Right padding") (list @secref[#:underline? #f]{S} "Skip") (list @secref[#:underline? #f]{T} "Tabulator") (list @secref[#:underline? #f]{U} "Unfold") (list @secref[#:underline? #f]{V} "Unfold recursively") (list @secref[#:underline? #f]{W} "Write") (list @secref[#:underline? #f]{X} "Space") (list @secref[#:underline? #f]{Y} "Decompose complex number") (list @secref[#:underline? #f]{Z} "Unfold recursively") (list @seclink[#:underline? #f "simple-compound" "(...)"] "compound instruction") (list @seclink[#:underline? #f "special-compound" "[...]"] "compound instruction") (list @seclink["simple-literal" "'κ ...'" #:underline? #f] "Literal data") (list @seclink["compound-literal" "^'κ ...'" #:underline? #f] "Literal data") (list @secref[#:underline? #f]{%} "Decompose in numerator and denominator") (list @secref[#:underline? #f]{/} "Newline") (list @secref[#:underline? #f]{|} "Newline but not double") (list @secref[#:underline? #f]{~} "Display all remaining data") (list @secref[#:underline? #f]{*ξ} "Repeat until no more data") (list @secref[#:underline? #f]{νξ} "Repeat ν times") (list @secref[#:underline? #f]{!} "If more data left") (list @secref[#:underline? #f]{?} "If no more data left") (list @secref[#:underline? #f]{+} "Sign mode on") (list @secref[#:underline? #f]{-} "Sign mode off") (list @secref[#:underline? #f]{$} "Preserve sign mode") (list @secref[#:underline? #f]{:} "Local exit") (list @secref[#:underline? #f]{;} "Top level exit") (list @secref[#:underline? #f]{&} "Tabulate to end of line") (list @seclink[#:underline? #f "@"] "Preserve tabulator") (list @secref[#:underline? #f]{<} "Relative tab backward") (list @secref[#:underline? #f]{>} "Relative tab forward") (list @secref[#:underline? #f]{=} "Read") (list @secref[#:underline? #f]{λ} "Call procedure"))] @verbatim[""]