5.7 Columnar Formatting

tab-to

space-to

(columnar <column> ...)

<column>

(fmt #t (columnar (dsp "abcndefn") (dsp "123n456n")))

outputs

abc 123 def 456

assuming a 16-char width (the left side gets half the width, or 8 spaces, and is left aligned). Note that we explicitly use DSP instead of the strings directly. This is because columnar treats raw strings as literals inserted into the given location on every line, to be used as borders, for example:

(fmt #t (columnar "/* " (dsp "abcndefn") " | " (dsp "123n456n") " */"))

would output

/* abc | 123 */ /* def | 456 */

You may also prefix any column with any of the symbols 'left , 'right or 'center to control the justification. The symbol 'infinite can be used to indicate the column generates an infinite stream of output.

You can further prefix any column with a width modifier. Any positive integer is treated as a fixed width, ignoring the available width. Any real number between 0 and 1 indicates a fraction of the available width (after subtracting out any fixed widths). Columns with unspecified width divide up the remaining width evenly.

Note that columnar builds its output incrementally, interleaving calls to the generators until each has produced a line, then concatenating that line together and outputting it. This is important because as noted above, some columns may produce an infinite stream of output, and in general you may want to format data larger than can fit into memory. Thus columnar would be suitable for line numbering a file of arbitrary size, or implementing the Unix yes(1) command, etc.

As an implementation detail, columnar uses first-class continuations to interleave the column output. The core fmt itself has no knowledge of or special support for columnar , which could complicate and potentially slow down simpler fmt operations. This is a testament to the power of call/cc - it can be used to implement coroutines or arbitrary control structures even where they were not planned for.

(tabular <column> ...)

columnar

(fmt #t (tabular "|" (dsp "a\

bc\

def\

") "|" (dsp "123n45n6n") "|"))

outputs

|a |123| |bc |45 | |def|6 |

This makes it easier to generate tables without knowing widths in advance. However, because it requires generating the entire output in advance to determine the correct column widths, tabular cannot format a table larger than would fit in memory.

(fmt-columns <column> ...)

columnar

<column>

(<line-formatter> <line-generator> [<infinite?>])

where <line-generator> is the column generator as above, and the <line-formatter> is how each line is formatted. Raw concatenation of each line is performed, without any spacing or width adjustment. <infinite?> , if true, indicates this generator produces an infinite number of lines and termination should be determined without it.

(wrap-lines <format> ...)

cat

fmt(1)

(justify <format> ...)

wrap-lines

(define func '(define (fold kons knil ls) (let lp ((ls ls) (acc knil)) (if (null? ls) acc (lp (cdr ls) (kons (car ls) acc)))))) (define doc (string-append "The fundamental list iterator. Applies KONS to each element " "of LS and the result of the previous application, beginning " "with KNIL. With KONS as CONS and KNIL as '(), equivalent to REVERSE.")) (fmt #t (columnar (pretty func) " ; " (justify doc)))

outputs

(define (fold kons knil ls) ; The fundamental list iterator. (let lp ((ls ls) (acc knil)) ; Applies KONS to each element of (if (null? ls) ; LS and the result of the previous acc ; application, beginning with KNIL. (lp (cdr ls) ; With KONS as CONS and KNIL as '(), (kons (car ls) acc))))) ; equivalent to REVERSE.

(fmt-file <pathname>)

<pathname>

columnar

(line-numbers [<start>])

<start>

1

The Unix nl(1) utility could be implemented as:

(fmt #t (columnar 6 'right 'infinite (line-numbers) " " (fmt-file "read-line.scm")))

1 2 (define (read-line . o) 3 (let ((port (if (pair? o) (car o) (current-input-port)))) 4 (let lp ((res '())) 5 (let ((c (read-char port))) 6 (if (or (eof-object? c) (eqv? c #

ewline)) 7 (list->string (reverse res)) 8 (lp (cons c res)))))))