diff --git a/README.md b/README.md
index 193badd..8ab48c9 100644
--- a/README.md
+++ b/README.md
@@ -111,11 +111,10 @@ Functional tests
A bunch of high level tests are implemented in the functional-tests directory.
These tests are written in Scheme. To run them you'll need to install
-chezscheme (http://www.scheme.com/). In addition they make use of the
-thunderchez (https://github.com/ovenpasta/thunderchez) library.
+chezscheme (http://www.scheme.com/). There is no longer a dependency on
+the ThunderChez library.
-Make sure the tools that you wish to test are in your PATH, and the thunderchez
-directory is in the CHEZSCHEMELIBDIRS environment variable.
+Make sure the tools that you wish to test are in your PATH.
Then,
diff --git a/functional-tests/fmt/README b/functional-tests/fmt/README
new file mode 100644
index 0000000..159df2d
--- /dev/null
+++ b/functional-tests/fmt/README
@@ -0,0 +1,14 @@
+
+ fmt
+ ---
+
+ Combinator Formatting Library
+
+ http://synthcode.com/scheme/fmt/
+
+
+This directory contains a portable combinator-based formatting library
+for Scheme. It has been tested on Chicken, Gauche, MzScheme 3.x and
+Scheme48.
+
+Documentation is in the file fmt.html.
diff --git a/functional-tests/fmt/VERSION b/functional-tests/fmt/VERSION
new file mode 100644
index 0000000..b60d719
--- /dev/null
+++ b/functional-tests/fmt/VERSION
@@ -0,0 +1 @@
+0.8.4
diff --git a/functional-tests/fmt/c.sls b/functional-tests/fmt/c.sls
new file mode 100644
index 0000000..95ceca9
--- /dev/null
+++ b/functional-tests/fmt/c.sls
@@ -0,0 +1,42 @@
+;;;; fmt-c.scm -- fmt module for emitting/pretty-printing C code
+;;
+;; Copyright (c) 2007 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+#!r6rs
+(library
+ (fmt c)
+ (export
+ fmt-in-macro? fmt-expression? fmt-return? fmt-default-type
+ fmt-newline-before-brace? fmt-braceless-bodies?
+ fmt-indent-space fmt-switch-indent-space fmt-op fmt-gen
+ c-in-expr c-in-stmt c-in-test
+ c-paren c-maybe-paren c-type c-literal? c-literal char->c-char
+ c-struct c-union c-class c-enum c-typedef c-cast
+ c-expr c-expr/sexp c-apply c-op c-indent c-current-indent-string
+ c-wrap-stmt c-open-brace c-close-brace
+ c-block c-braced-block c-begin
+ c-fun c-var c-prototype c-param c-param-list
+ c-while c-for c-if c-switch
+ c-case c-case/fallthrough c-default
+ c-break c-continue c-return c-goto c-label
+ c-static c-const c-extern c-volatile c-auto c-restrict c-inline
+ c++ c-- c+ c- c* c/ c% c& c^ c~ c! c&& c<< c>> c== c!= ; |c\|| |c\|\||
+ c< c> c<= c>= c= c+= c-= c*= c/= c%= c&= c^= c<<= c>>= ;++c --c ; |c\|=|
+ c++/post c--/post c. c->
+ c-bit-or c-or c-bit-or=
+ cpp-if cpp-ifdef cpp-ifndef cpp-elif cpp-endif cpp-else cpp-undef
+ cpp-include cpp-define cpp-wrap-header cpp-pragma cpp-line
+ cpp-error cpp-warning cpp-stringify cpp-sym-cat
+ c-comment c-block-comment c-attribute
+ )
+
+ (import (chezscheme)
+ (fmt fmt)
+ (srfi private include)
+ (only (srfi s1 lists) every)
+ (only (srfi s13 strings) substring/shared string-index string-index-right))
+
+ (include/resolve ("fmt") "fmt-c.scm")
+
+ )
diff --git a/functional-tests/fmt/fmt-c.scm b/functional-tests/fmt/fmt-c.scm
new file mode 100644
index 0000000..723bbaa
--- /dev/null
+++ b/functional-tests/fmt/fmt-c.scm
@@ -0,0 +1,874 @@
+;;;; fmt-c.scm -- fmt module for emitting/pretty-printing C code
+;;
+;; Copyright (c) 2007 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; additional state information
+
+(define (fmt-in-macro? st) (fmt-ref st 'in-macro?))
+(define (fmt-expression? st) (fmt-ref st 'expression?))
+(define (fmt-return? st) (fmt-ref st 'return?))
+(define (fmt-default-type st) (fmt-ref st 'default-type 'int))
+(define (fmt-newline-before-brace? st) (fmt-ref st 'newline-before-brace?))
+(define (fmt-braceless-bodies? st) (fmt-ref st 'braceless-bodies?))
+(define (fmt-non-spaced-ops? st) (fmt-ref st 'non-spaced-ops?))
+(define (fmt-no-wrap? st) (fmt-ref st 'no-wrap?))
+(define (fmt-indent-space st) (fmt-ref st 'indent-space))
+(define (fmt-switch-indent-space st) (fmt-ref st 'switch-indent-space))
+(define (fmt-op st) (fmt-ref st 'op 'stmt))
+(define (fmt-gen st) (fmt-ref st 'gen))
+
+(define (c-in-expr proc) (fmt-let 'expression? #t proc))
+(define (c-in-stmt proc) (fmt-let 'expression? #f proc))
+(define (c-in-test proc) (fmt-let 'in-cond? #t (c-in-expr proc)))
+(define (c-with-op op proc) (fmt-let 'op op proc))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; be smart about operator precedence
+
+(define (c-op-precedence x)
+ (if (string? x)
+ (cond
+ ((or (string=? x ".") (string=? x "->")) 10)
+ ((or (string=? x "++") (string=? x "--")) 20)
+ ((string=? x "|") 65)
+ ((string=? x "||") 75)
+ ((string=? x "|=") 85)
+ ((or (string=? x "+=") (string=? x "-=")) 85)
+ (else 95))
+ (case x
+ ;;((|::|) 5) ; C++
+ ((paren bracket) 5)
+ ((dot arrow post-decrement post-increment) 10)
+ ((**) 15) ; Perl
+ ((unary+ unary- ! ~ cast unary-* unary-& sizeof) 20) ; ++ --
+ ((=~ !~) 25) ; Perl
+ ((* / %) 30)
+ ((+ -) 35)
+ ((<< >>) 40)
+ ((< > <= >=) 45)
+ ((lt gt le ge) 45) ; Perl
+ ((== !=) 50)
+ ((eq ne cmp) 50) ; Perl
+ ((&) 55)
+ ((^) 60)
+ ;;((|\||) 65)
+ ((&&) 70)
+ ;;((|\|\||) 75)
+ ;;((.. ...) 77) ; Perl
+ ((?) 80)
+ ((= *= /= %= &= ^= <<= >>=) 85) ; |\|=| ; += -=
+ ((comma) 90)
+ ((=>) 90) ; Perl
+ ((not) 92) ; Perl
+ ((and) 93) ; Perl
+ ((or xor) 94) ; Perl
+ (else 95))))
+
+(define (c-op< x y) (< (c-op-precedence x) (c-op-precedence y)))
+
+(define (c-paren x) (cat "(" x ")"))
+
+(define (c-maybe-paren op x)
+ (lambda (st)
+ ((fmt-let 'op op
+ (if (or (fmt-in-macro? st) (c-op< (fmt-op st) op))
+ (c-paren x)
+ x))
+ st)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; default literals writer
+
+(define (c-control-operator? x)
+ (memq x '(if while switch repeat do for fun begin)))
+
+(define (c-literal? x)
+ (or (number? x) (string? x) (char? x) (boolean? x)))
+
+(define (char->c-char c)
+ (string-append "'" (c-escape-char c #\') "'"))
+
+(define (c-escape-char c quote-char)
+ (let ((n (char->integer c)))
+ (if (<= 32 n 126)
+ (if (or (eqv? c quote-char) (eqv? c #\\))
+ (string #\\ c)
+ (string c))
+ (case n
+ ((7) "\\a") ((8) "\\b") ((9) "\\t") ((10) "\\n")
+ ((11) "\\v") ((12) "\\f") ((13) "\\r")
+ (else (string-append "\\x" (number->string (char->integer c) 16)))))))
+
+(define (c-format-number x)
+ (if (and (integer? x) (exact? x))
+ (lambda (st)
+ ((case (fmt-radix st)
+ ((16) (cat "0x" (string-upcase (number->string x 16))))
+ ((8) (cat "0" (number->string x 8)))
+ (else (dsp (number->string x))))
+ st))
+ (dsp (number->string x))))
+
+(define (c-format-string x)
+ (lambda (st) ((cat #\" (apply-cat (c-string-escaped x)) #\") st)))
+
+(define (c-string-escaped x)
+ (let loop ((parts '()) (idx (string-length x)))
+ (cond ((string-index-right x c-needs-string-escape? 0 idx)
+ => (lambda (special-idx)
+ (loop (cons (c-escape-char (string-ref x special-idx) #\")
+ (cons (substring/shared x (+ special-idx 1) idx)
+ parts))
+ special-idx)))
+ (else
+ (cons (substring/shared x 0 idx) parts)))))
+
+(define (c-needs-string-escape? c)
+ (if (<= 32 (char->integer c) 127) (memv c '(#\" #\\)) #t))
+
+(define (c-simple-literal x)
+ (c-wrap-stmt
+ (cond ((char? x) (dsp (char->c-char x)))
+ ((boolean? x) (dsp (if x "1" "0")))
+ ((number? x) (c-format-number x))
+ ((string? x) (c-format-string x))
+ ((null? x) (dsp "NULL"))
+ ((eof-object? x) (dsp "EOF"))
+ (else (dsp (write-to-string x))))))
+
+(define (c-literal x)
+ (lambda (st)
+ ((if (and (fmt-in-macro? st) (c-op< 'paren (fmt-op st))
+ (not (c-literal? x)))
+ (c-paren (c-simple-literal x))
+ (c-simple-literal x))
+ st)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; default expression generator
+
+(define (c-expr/sexp x)
+ (if (procedure? x)
+ x
+ (lambda (st)
+ (cond
+ ((pair? x)
+ (case (car x)
+ ((if) ((apply c-if (cdr x)) st))
+ ((for) ((apply c-for (cdr x)) st))
+ ((while) ((apply c-while (cdr x)) st))
+ ((switch) ((apply c-switch (cdr x)) st))
+ ((case) ((apply c-case (cdr x)) st))
+ ((case/fallthrough) ((apply c-case/fallthrough (cdr x)) st))
+ ((default) ((apply c-default (cdr x)) st))
+ ((break) (c-break st))
+ ((continue) (c-continue st))
+ ((return) ((apply c-return (cdr x)) st))
+ ((goto) ((apply c-goto (cdr x)) st))
+ ((typedef) ((apply c-typedef (cdr x)) st))
+ ((struct union class) ((apply c-struct/aux x) st))
+ ((enum) ((apply c-enum (cdr x)) st))
+ ((inline auto restrict register volatile extern static)
+ ((cat (car x) " " (apply-cat (cdr x))) st))
+ ;; non C-keywords must have some character invalid in a C
+ ;; identifier to avoid conflicts - by default we prefix %
+ ((vector-ref)
+ ((c-wrap-stmt
+ (cat (c-expr (cadr x)) "[" (c-expr (caddr x)) "]"))
+ st))
+ ((vector-set!)
+ ((c= (c-in-expr
+ (cat (c-expr (cadr x)) "[" (c-expr (caddr x)) "]"))
+ (c-expr (cadddr x)))
+ st))
+ ((extern/C) ((apply c-extern/C (cdr x)) st))
+ ((%apply) ((apply c-apply (cdr x)) st))
+ ((%define) ((apply cpp-define (cdr x)) st))
+ ((%include) ((apply cpp-include (cdr x)) st))
+ ((%fun) ((apply c-fun (cdr x)) st))
+ ((%cond)
+ (let lp ((ls (cdr x)) (res '()))
+ (if (null? ls)
+ ((apply c-if (reverse res)) st)
+ (lp (cdr ls)
+ (cons (if (pair? (cddar ls))
+ (apply c-begin (cdar ls))
+ (cadar ls))
+ (cons (caar ls) res))))))
+ ((%prototype) ((apply c-prototype (cdr x)) st))
+ ((%var) ((apply c-var (cdr x)) st))
+ ((%begin) ((apply c-begin (cdr x)) st))
+ ((%attribute) ((apply c-attribute (cdr x)) st))
+ ((%line) ((apply cpp-line (cdr x)) st))
+ ((%pragma %error %warning)
+ ((apply cpp-generic (substring/shared (symbol->string (car x)) 1)
+ (cdr x)) st))
+ ((%if %ifdef %ifndef %elif)
+ ((apply cpp-if/aux (substring/shared (symbol->string (car x)) 1)
+ (cdr x)) st))
+ ((%endif) ((apply cpp-endif (cdr x)) st))
+ ((%block) ((apply c-braced-block (cdr x)) st))
+ ((%comment) ((apply c-comment (cdr x)) st))
+ ((:) ((apply c-label (cdr x)) st))
+ ((%cast) ((apply c-cast (cdr x)) st))
+ ((+ - & * / % ! ~ ^ && < > <= >= == != << >>
+ = *= /= %= &= ^= >>= <<=) ; |\|| |\|\|| |\|=|
+ ((apply c-op x) st))
+ ((bitwise-and bit-and) ((apply c-op '& (cdr x)) st))
+ ((bitwise-ior bit-or) ((apply c-op "|" (cdr x)) st))
+ ((bitwise-xor bit-xor) ((apply c-op '^ (cdr x)) st))
+ ((bitwise-not bit-not) ((apply c-op '~ (cdr x)) st))
+ ((arithmetic-shift) ((apply c-op '<< (cdr x)) st))
+ ((bitwise-ior= bit-or=) ((apply c-op "|=" (cdr x)) st))
+ ((%or) ((apply c-op "||" (cdr x)) st))
+ ((%. %field) ((apply c-op "." (cdr x)) st))
+ ((%->) ((apply c-op "->" (cdr x)) st))
+ (else
+ (cond
+ ((eq? (car x) (string->symbol "."))
+ ((apply c-op "." (cdr x)) st))
+ ((eq? (car x) (string->symbol "->"))
+ ((apply c-op "->" (cdr x)) st))
+ ((eq? (car x) (string->symbol "++"))
+ ((apply c-op "++" (cdr x)) st))
+ ((eq? (car x) (string->symbol "--"))
+ ((apply c-op "--" (cdr x)) st))
+ ((eq? (car x) (string->symbol "+="))
+ ((apply c-op "+=" (cdr x)) st))
+ ((eq? (car x) (string->symbol "-="))
+ ((apply c-op "-=" (cdr x)) st))
+ (else ((c-apply x) st))))))
+ ((vector? x)
+ ((c-wrap-stmt
+ (fmt-try-fit
+ (fmt-let 'no-wrap? #t
+ (cat "{" (fmt-join c-expr (vector->list x) ", ") "}"))
+ (lambda (st)
+ (let* ((col (fmt-col st))
+ (sep (string-append "," (make-nl-space col))))
+ ((cat "{" (fmt-join c-expr (vector->list x) sep)
+ "}" nl)
+ st)))))
+ st))
+ (else
+ ((c-literal x) st))))))
+
+(define (c-apply ls)
+ (c-wrap-stmt
+ (c-with-op
+ 'paren
+ (cat (c-expr (car ls))
+ (let ((flat (fmt-let 'no-wrap? #t (fmt-join c-expr (cdr ls) ", "))))
+ (fmt-if
+ fmt-no-wrap?
+ (c-paren flat)
+ (c-paren
+ (fmt-try-fit
+ flat
+ (lambda (st)
+ (let* ((col (fmt-col st))
+ (sep (string-append "," (make-nl-space col))))
+ ((fmt-join c-expr (cdr ls) sep) st)))))))))))
+
+(define (c-expr x)
+ (lambda (st) (((or (fmt-gen st) c-expr/sexp) x) st)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; comments, with Emacs-friendly escaping of nested comments
+
+(define (make-comment-writer st)
+ (let ((output (fmt-ref st 'writer)))
+ (lambda (str st)
+ (let ((lim (- (string-length str) 1)))
+ (let lp ((i 0) (st st))
+ (let ((j (string-index str #\/ i)))
+ (if j
+ (let ((st (if (and (> j 0)
+ (eqv? #\* (string-ref str (- j 1))))
+ (output
+ "\\/"
+ (output (substring/shared str i j) st))
+ (output (substring/shared str i (+ j 1)) st))))
+ (lp (+ j 1)
+ (if (and (< j lim) (eqv? #\* (string-ref str (+ j 1))))
+ (output "\\" st)
+ st)))
+ (output (substring/shared str i) st))))))))
+
+(define (c-comment . args)
+ (lambda (st)
+ ((cat "/*" (fmt-let 'writer (make-comment-writer st)
+ (apply-cat args))
+ "*/")
+ st)))
+
+(define (make-block-comment-writer st)
+ (let ((output (make-comment-writer st))
+ (indent (string-append (make-nl-space (+ (fmt-col st) 1)) "* ")))
+ (lambda (str st)
+ (let ((lim (string-length str)))
+ (let lp ((i 0) (st st))
+ (let ((j (string-index str #\newline i)))
+ (if j
+ (lp (+ j 1)
+ (output indent (output (substring/shared str i j) st)))
+ (output (substring/shared str i) st))))))))
+
+(define (c-block-comment . args)
+ (lambda (st)
+ (let ((col (fmt-col st))
+ (row (fmt-row st))
+ (indent (c-current-indent-string st)))
+ ((cat "/* "
+ (fmt-let 'writer (make-block-comment-writer st) (apply-cat args))
+ (lambda (st)
+ (cond
+ ((= row (fmt-row st)) ((dsp " */") st))
+ ;;((= (+ 3 col) (fmt-col st)) ((dsp "*/") st))
+ (else ((cat fl indent " */") st)))))
+ st))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; preprocessor
+
+(define (make-cpp-writer st)
+ (let ((output (fmt-ref st 'writer)))
+ (lambda (str st)
+ (let lp ((i 0) (st st))
+ (let ((j (string-index str #\newline i)))
+ (if j
+ (lp (+ j 1)
+ (output
+ nl-str
+ (output " \\" (output (substring/shared str i j) st))))
+ (output (substring/shared str i) st)))))))
+
+(define (cpp-include file)
+ (if (string? file)
+ (cat fl "#include " (wrt file) fl)
+ (cat fl "#include <" file ">" fl)))
+
+(define (list-dot x)
+ (cond ((pair? x) (list-dot (cdr x)))
+ ((null? x) #f)
+ (else x)))
+
+(define (replace-tree from to x)
+ (let replace ((x x))
+ (cond ((eq? x from) to)
+ ((pair? x) (cons (replace (car x)) (replace (cdr x))))
+ (else x))))
+
+(define (cpp-define x . body)
+ (define (name-of x) (c-expr (if (pair? x) (cadr x) x)))
+ (lambda (st)
+ (let* ((body (cond
+ ((and (pair? x) (list-dot x))
+ => (lambda (dot)
+ (if (eq? dot '...)
+ body
+ (replace-tree dot '__VA_ARGS__ body))))
+ (else body)))
+ (tail
+ (if (pair? body)
+ (cat " "
+ (fmt-let 'writer (make-cpp-writer st)
+ (fmt-let 'in-macro? (pair? x)
+ ((if (or (not (pair? x))
+ (and (null? (cdr body))
+ (c-literal? (car body))))
+ (lambda (x) x)
+ c-paren)
+ (c-in-expr (apply c-begin body))))))
+ (lambda (x) x))))
+ ((c-in-expr
+ (if (pair? x)
+ (cat fl "#define " (name-of (car x))
+ (c-paren
+ (fmt-join/dot name-of
+ (lambda (dot) (dsp "..."))
+ (cdr x)
+ ", "))
+ tail fl)
+ (cat fl "#define " (c-expr x) tail fl)))
+ st))))
+
+(define (cpp-expr x)
+ (if (or (symbol? x) (string? x)) (dsp x) (c-expr x)))
+
+(define (cpp-if/aux name check . o)
+ (let* ((pass (and (pair? o) (car o)))
+ (comment (if (member name '("ifdef" "ifndef"))
+ (cat " "
+ (c-comment
+ " " (if (equal? name "ifndef") "! " "")
+ check " "))
+ ""))
+ (endif (if pass (cat fl "#endif" comment) ""))
+ (tail (cond
+ ((and (pair? o) (pair? (cdr o)))
+ (if (pair? (cddr o))
+ (apply cpp-elif (cdr o))
+ (cat (cpp-else) (cadr o) endif)))
+ (else endif))))
+ (lambda (st)
+ (let ((indent (c-current-indent-string st)))
+ ((cat fl "#" name " " (cpp-expr check) fl
+ (if pass (cat indent pass) "") fl
+ tail fl)
+ st)))))
+
+(define (cpp-if check . o)
+ (apply cpp-if/aux "if" check o))
+(define (cpp-ifdef check . o)
+ (apply cpp-if/aux "ifdef" check o))
+(define (cpp-ifndef check . o)
+ (apply cpp-if/aux "ifndef" check o))
+(define (cpp-elif check . o)
+ (apply cpp-if/aux "elif" check o))
+(define (cpp-else . o)
+ (cat fl "#else " (if (pair? o) (c-comment (car o)) "") fl))
+(define (cpp-endif . o)
+ (cat fl "#endif " (if (pair? o) (c-comment (car o)) "") fl))
+
+(define (cpp-wrap-header name . body)
+ (let ((name name)) ; consider auto-mangling
+ (cpp-ifndef name (c-begin (cpp-define name) nl (apply c-begin body) nl))))
+
+(define (cpp-line num . o)
+ (cat fl "#line " num (if (pair? o) (cat " " (car o)) "") fl))
+
+(define (cpp-generic name . ls)
+ (cat fl "#" name (apply-cat ls) fl))
+
+(define (cpp-undef . args) (apply cpp-generic "undef" args))
+(define (cpp-pragma . args) (apply cpp-generic "pragma" args))
+(define (cpp-error . args) (apply cpp-generic "error" args))
+(define (cpp-warning . args) (apply cpp-generic "warning" args))
+
+(define (cpp-stringify x)
+ (cat "#" x))
+
+(define (cpp-sym-cat . args)
+ (fmt-join dsp args " ## "))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; general indentation and brace rules
+
+(define (c-current-indent-string st . o)
+ (make-space (max 0 (+ (fmt-col st) (if (pair? o) (car o) 0)))))
+
+(define (c-indent st . o)
+ (dsp (make-space (max 0 (+ (fmt-col st) (or (fmt-indent-space st) 4)
+ (if (pair? o) (car o) 0))))))
+
+(define (c-indent/switch st)
+ (dsp (make-space (+ (fmt-col st) (or (fmt-switch-indent-space st) 4)))))
+
+(define (c-open-brace st)
+ (if (fmt-newline-before-brace? st)
+ (cat nl (c-current-indent-string st) "{" nl)
+ (cat " {" nl)))
+
+(define (c-close-brace st)
+ (dsp "}"))
+
+(define (c-wrap-stmt x)
+ (fmt-if fmt-expression?
+ (c-expr x)
+ (cat (fmt-if fmt-return? "return " "")
+ (c-in-expr (c-expr x)) ";" nl)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; code blocks
+
+(define (c-block . args)
+ (apply c-block/aux 0 args))
+
+(define (c-block/aux offset header body0 . body)
+ (let ((inner (apply c-begin body0 body)))
+ (if (or (pair? body)
+ (not (or (c-literal? body0)
+ (and (pair? body0)
+ (not (c-control-operator? (car body0)))))))
+ (c-braced-block/aux offset header inner)
+ (lambda (st)
+ (if (fmt-braceless-bodies? st)
+ ((cat header fl (c-indent st offset) inner fl) st)
+ ((c-braced-block/aux offset header inner) st))))))
+
+(define (c-braced-block . args)
+ (apply c-braced-block/aux 0 args))
+
+(define (c-braced-block/aux offset header . body)
+ (lambda (st)
+ ((cat header (c-open-brace st) (c-indent st offset)
+ (apply c-begin body) fl
+ (c-current-indent-string st offset) (c-close-brace st))
+ st)))
+
+(define (c-begin . args)
+ (apply c-begin/aux #f args))
+
+(define (c-begin/aux ret? body0 . body)
+ (if (null? body)
+ (c-expr body0)
+ (lambda (st)
+ (if (fmt-expression? st)
+ ((fmt-try-fit
+ (fmt-let 'no-wrap? #t (fmt-join c-expr (cons body0 body) ", "))
+ (lambda (st)
+ (let ((indent (c-current-indent-string st)))
+ ((fmt-join c-expr (cons body0 body) (cat "," nl indent)) st))))
+ st)
+ (let ((orig-ret? (fmt-return? st)))
+ ((fmt-join/last c-expr
+ (lambda (x) (fmt-let 'return? orig-ret? (c-expr x)))
+ (cons body0 body)
+ (cat fl (c-current-indent-string st)))
+ (fmt-set! st 'return? (and ret? orig-ret?))))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; data structures
+
+(define (c-struct/aux type x . o)
+ (let* ((name (if (null? o) (if (or (symbol? x) (string? x)) x #f) x))
+ (body (if name (car o) x))
+ (o (if (null? o) o (cdr o))))
+ (c-wrap-stmt
+ (cat
+ (c-braced-block
+ (cat type (if (and name (not (equal? name ""))) (cat " " name) ""))
+ (cat
+ (c-in-stmt
+ (if (list? body)
+ (apply c-begin (map c-wrap-stmt (map c-param body)))
+ (c-wrap-stmt (c-expr body))))))
+ (if (pair? o) (cat " " (apply c-begin o)) (dsp ""))))))
+
+(define (c-struct . args) (apply c-struct/aux "struct" args))
+(define (c-union . args) (apply c-struct/aux "union" args))
+(define (c-class . args) (apply c-struct/aux "class" args))
+
+(define (c-enum x . o)
+ (define (c-enum-one x)
+ (if (pair? x) (cat (car x) " = " (c-expr (cadr x))) (dsp x)))
+ (let* ((name (if (null? o) (if (or (symbol? x) (string? x)) x #f) x))
+ (vals (if name (car o) x)))
+ (c-wrap-stmt
+ (cat
+ (c-braced-block
+ (if name (cat "enum " name) (dsp "enum"))
+ (c-in-expr (apply c-begin (map c-enum-one vals))))))))
+
+(define (c-attribute . args)
+ (cat "__attribute__ ((" (fmt-join c-expr args ", ") "))"))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; basic control structures
+
+(define (c-while check . body)
+ (cat (c-block (cat "while (" (c-in-test (c-expr check)) ")")
+ (c-in-stmt (apply c-begin body)))
+ fl))
+
+(define (c-for init check update . body)
+ (cat
+ (c-block
+ (c-in-expr
+ (cat "for (" (c-expr init) "; " (c-in-test (c-expr check)) "; "
+ (c-expr update ) ")"))
+ (c-in-stmt (apply c-begin body)))
+ fl))
+
+(define (c-param x)
+ (cond
+ ((procedure? x) x)
+ ((pair? x) (c-type (car x) (cadr x)))
+ (else (cat (lambda (st) ((c-type (fmt-default-type st)) st)) " " x))))
+
+(define (c-param-list ls)
+ (c-in-expr (fmt-join/dot c-param (lambda (dot) (dsp "...")) ls ", ")))
+
+(define (c-fun type name params . body)
+ (cat (c-block (c-in-expr (c-prototype type name params))
+ (fmt-let 'return? (not (eq? 'void type))
+ (c-in-stmt (apply c-begin body))))
+ fl))
+
+(define (c-prototype type name params . o)
+ (c-wrap-stmt
+ (cat (c-type type) " " (c-expr name) " (" (c-param-list params) ")"
+ (fmt-join/prefix c-expr o " "))))
+
+(define (c-static x) (cat "static " (c-expr x)))
+(define (c-const x) (cat "const " (c-expr x)))
+(define (c-restrict x) (cat "restrict " (c-expr x)))
+(define (c-volatile x) (cat "volatile " (c-expr x)))
+(define (c-auto x) (cat "auto " (c-expr x)))
+(define (c-inline x) (cat "inline " (c-expr x)))
+(define (c-extern x) (cat "extern " (c-expr x)))
+(define (c-extern/C . body)
+ (cat "extern \"C\" {" nl (apply c-begin body) nl "}" nl))
+
+(define (c-type type . o)
+ (let ((name (and (pair? o) (car o))))
+ (cond
+ ((pair? type)
+ (case (car type)
+ ((%fun)
+ (cat (c-type (cadr type) #f)
+ " (*" (or name "") ")("
+ (fmt-join (lambda (x) (c-type x #f)) (caddr type) ", ") ")"))
+ ((%array)
+ (let ((name (cat name "[" (if (pair? (cddr type))
+ (c-expr (caddr type))
+ "")
+ "]")))
+ (c-type (cadr type) name)))
+ ((%pointer *)
+ (let ((name (cat "*" (if name (c-expr name) ""))))
+ (c-type (cadr type)
+ (if (and (pair? (cadr type)) (eq? '%array (caadr type)))
+ (c-paren name)
+ name))))
+ ((enum) (apply c-enum name (cdr type)))
+ ((struct union class)
+ (cat (apply c-struct/aux (car type) (cdr type)) " " name))
+ (else (fmt-join/last c-expr (lambda (x) (c-type x name)) type " "))))
+ ((not type)
+ (lambda (st) ((c-type (or (fmt-default-type st) 'int) name) st)))
+ (else
+ (cat (if (eq? '%pointer type) '* type) (if name (cat " " name) ""))))))
+
+(define (c-var type name . init)
+ (c-wrap-stmt
+ (if (pair? init)
+ (cat (c-type type name) " = " (c-expr (car init)))
+ (c-type type (if (pair? name)
+ (fmt-join c-expr name ", ")
+ (c-expr name))))))
+
+(define (c-cast type expr)
+ (cat "(" (c-type type) ")" (c-expr expr)))
+
+(define (c-typedef type alias . o)
+ (c-wrap-stmt
+ (cat "typedef " (c-type type alias) (fmt-join/prefix c-expr o " "))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; Generalized IF: allows multiple tail forms for if/else if/.../else
+;; blocks. A final ELSE can be signified with a test of #t or 'else,
+;; or by simply using an odd number of expressions (by which the
+;; normal 2 or 3 clause IF forms are special cases).
+
+(define (c-if/stmt c p . rest)
+ (lambda (st)
+ (let ((indent (c-current-indent-string st)))
+ ((let lp ((c c) (p p) (ls rest))
+ (if (or (eq? c 'else) (eq? c #t))
+ (if (not (null? ls))
+ (error "forms after else clause in IF" c p ls)
+ (cat (c-block/aux -1 " else" p) fl))
+ (let ((tail (if (pair? ls)
+ (if (pair? (cdr ls))
+ (lp (car ls) (cadr ls) (cddr ls))
+ (lp 'else (car ls) '()))
+ fl)))
+ (cat (c-block/aux
+ (if (eq? ls rest) 0 -1)
+ (cat (if (eq? ls rest) (lambda (x) x) " else ")
+ "if (" (c-in-test (c-expr c)) ")") p)
+ tail))))
+ st))))
+
+(define (c-if/expr c p . rest)
+ (let lp ((c c) (p p) (ls rest))
+ (cond
+ ((or (eq? c 'else) (eq? c #t))
+ (if (not (null? ls))
+ (error "forms after else clause in IF" c p ls)
+ (c-expr p)))
+ ((pair? ls)
+ (cat (c-in-test (c-expr c)) " ? " (c-expr p) " : "
+ (if (pair? (cdr ls))
+ (lp (car ls) (cadr ls) (cddr ls))
+ (lp 'else (car ls) '()))))
+ (else
+ (c-or (c-in-test (c-expr c)) (c-expr p))))))
+
+(define (c-if . args)
+ (fmt-if fmt-expression?
+ (apply c-if/expr args)
+ (apply c-if/stmt args)))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; switch statements, automatic break handling
+
+(define (c-label name)
+ (lambda (st)
+ (let ((indent (make-space (max 0 (- (fmt-col st) 2)))))
+ ((cat fl indent name ":" fl) st))))
+
+(define c-break
+ (c-wrap-stmt (dsp "break")))
+(define c-continue
+ (c-wrap-stmt (dsp "continue")))
+(define (c-return . result)
+ (if (pair? result)
+ (c-wrap-stmt (cat "return " (c-expr (car result))))
+ (c-wrap-stmt (dsp "return"))))
+(define (c-goto label)
+ (c-wrap-stmt (cat "goto " (c-expr label))))
+
+(define (c-switch val . clauses)
+ (lambda (st)
+ ((cat "switch (" (c-in-expr val) ")" (c-open-brace st)
+ (c-indent/switch st)
+ (c-in-stmt (apply c-begin/aux #t (map c-switch-clause clauses))) fl
+ (c-current-indent-string st) (c-close-brace st) fl)
+ st)))
+
+(define (c-switch-clause/breaks x)
+ (lambda (st)
+ (let* ((break?
+ (and (car x)
+ (not (member (cadr x) '(case/fallthrough
+ default/fallthrough
+ else/fallthrough)))))
+ (explicit-case? (member (cadr x) '(case case/fallthrough)))
+ (indent (c-current-indent-string st))
+ (indent-body (c-indent st))
+ (sep (string-append ":" nl-str indent)))
+ ((cat (c-in-expr
+ (fmt-join/suffix
+ dsp
+ (cond
+ ((pair? (cadr x))
+ (map (lambda (y) (cat (dsp "case ") (c-expr y)))
+ (cadr x)))
+ (explicit-case?
+ (map (lambda (y) (cat (dsp "case ") (c-expr y)))
+ (if (list? (caddr x))
+ (caddr x)
+ (list (caddr x)))))
+ ((member (cadr x)
+ '(default else default/fallthrough else/fallthrough))
+ (list (dsp "default")))
+ (else
+ (error
+ "unknown switch clause, expected a list or default but got"
+ (cadr x))))
+ sep))
+ (make-space (or (fmt-indent-space st) 4))
+ (fmt-join c-expr
+ (if explicit-case? (cdddr x) (cddr x))
+ indent-body)
+ (if (and break? (not (fmt-return? st)))
+ (cat fl indent-body c-break)
+ ""))
+ st))))
+
+(define (c-switch-clause x)
+ (if (procedure? x) x (c-switch-clause/breaks (cons #t x))))
+(define (c-switch-clause/no-break x)
+ (if (procedure? x) x (c-switch-clause/breaks (cons #f x))))
+
+(define (c-case x . body)
+ (c-switch-clause (cons (if (pair? x) x (list x)) body)))
+(define (c-case/fallthrough x . body)
+ (c-switch-clause/no-break (cons (if (pair? x) x (list x)) body)))
+(define (c-default . body)
+ (c-switch-clause/breaks (cons #t (cons 'else body))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; operators
+
+(define (c-op op first . rest)
+ (if (null? rest)
+ (c-unary-op op first)
+ (apply c-binary-op op first rest)))
+
+(define (c-binary-op op . ls)
+ (define (lit-op? x) (or (c-literal? x) (symbol? x)))
+ (let ((str (display-to-string op)))
+ (c-wrap-stmt
+ (c-maybe-paren
+ op
+ (if (or (equal? str ".") (equal? str "->"))
+ (fmt-join c-expr ls str)
+ (let ((flat
+ (fmt-let 'no-wrap? #t
+ (lambda (st)
+ ((fmt-join c-expr
+ ls
+ (if (and (fmt-non-spaced-ops? st)
+ (every lit-op? ls))
+ str
+ (string-append " " str " ")))
+ st)))))
+ (fmt-if
+ fmt-no-wrap?
+ flat
+ (fmt-try-fit
+ flat
+ (lambda (st)
+ ((fmt-join c-expr
+ ls
+ (cat nl (make-space (+ 2 (fmt-col st))) str " "))
+ st))))))))))
+
+(define (c-unary-op op x)
+ (c-wrap-stmt
+ (cat (display-to-string op) (c-maybe-paren op (c-expr x)))))
+
+;; some convenience definitions
+
+(define (c++ . args) (apply c-op "++" args))
+(define (c-- . args) (apply c-op "--" args))
+(define (c+ . args) (apply c-op '+ args))
+(define (c- . args) (apply c-op '- args))
+(define (c* . args) (apply c-op '* args))
+(define (c/ . args) (apply c-op '/ args))
+(define (c% . args) (apply c-op '% args))
+(define (c& . args) (apply c-op '& args))
+;; (define (|c\|| . args) (apply c-op '|\|| args))
+(define (c^ . args) (apply c-op '^ args))
+(define (c~ . args) (apply c-op '~ args))
+(define (c! . args) (apply c-op '! args))
+(define (c&& . args) (apply c-op '&& args))
+;; (define (|c\|\|| . args) (apply c-op '|\|\|| args))
+(define (c<< . args) (apply c-op '<< args))
+(define (c>> . args) (apply c-op '>> args))
+(define (c== . args) (apply c-op '== args))
+(define (c!= . args) (apply c-op '!= args))
+(define (c< . args) (apply c-op '< args))
+(define (c> . args) (apply c-op '> args))
+(define (c<= . args) (apply c-op '<= args))
+(define (c>= . args) (apply c-op '>= args))
+(define (c= . args) (apply c-op '= args))
+(define (c+= . args) (apply c-op "+=" args))
+(define (c-= . args) (apply c-op "-=" args))
+(define (c*= . args) (apply c-op '*= args))
+(define (c/= . args) (apply c-op '/= args))
+(define (c%= . args) (apply c-op '%= args))
+(define (c&= . args) (apply c-op '&= args))
+;; (define (|c\|=| . args) (apply c-op '|\|=| args))
+(define (c^= . args) (apply c-op '^= args))
+(define (c<<= . args) (apply c-op '<<= args))
+(define (c>>= . args) (apply c-op '>>= args))
+
+(define (c. . args) (apply c-op "." args))
+(define (c-> . args) (apply c-op "->" args))
+
+(define (c-bit-or . args) (apply c-op "|" args))
+(define (c-or . args) (apply c-op "||" args))
+(define (c-bit-or= . args) (apply c-op "|=" args))
+
+(define (c++/post x)
+ (cat (c-maybe-paren 'post-increment (c-expr x)) "++"))
+(define (c--/post x)
+ (cat (c-maybe-paren 'post-decrement (c-expr x)) "--"))
+
diff --git a/functional-tests/fmt/fmt-color.scm b/functional-tests/fmt/fmt-color.scm
new file mode 100644
index 0000000..57191f1
--- /dev/null
+++ b/functional-tests/fmt/fmt-color.scm
@@ -0,0 +1,77 @@
+;;;; fmt-color.scm -- colored output
+;;
+;; Copyright (c) 2006-2007 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+(define (fmt-color st) (fmt-ref st 'color))
+(define (fmt-in-html? st) (fmt-ref st 'in-html?))
+(define (fmt-use-html-font? st) (fmt-ref st 'use-html-font?))
+
+(define (color->ansi x)
+ (if (number? x)
+ (let ((r (arithmetic-shift x -16))
+ (g (bitwise-and (arithmetic-shift x -8) #xFF))
+ (b (bitwise-and x #xFF)))
+ ;; just picks the highest color value - need to detect blends
+ (color->ansi
+ (cond
+ ((> r g) (if (> r b) 'red 'blue))
+ ((> g b) 'green)
+ (else 'blue))))
+ (case x
+ ((bold) "1")
+ ((dark) "2")
+ ((underline) "4")
+ ((black) "30")
+ ((red) "31")
+ ((green) "32")
+ ((yellow) "33")
+ ((blue) "34")
+ ((magenta) "35")
+ ((cyan) "36")
+ ((white) "37")
+ (else "0"))))
+
+(define (ansi-escape color)
+ (cat (integer->char 27) "[" (color->ansi color) "m"))
+
+(define (fmt-in-html . args)
+ (fmt-let 'in-html? #t (apply-cat args)))
+
+(define (fmt-colored color . args)
+ (fmt-if fmt-in-html?
+ (cond
+ ((eq? color 'bold)
+ (cat "" (apply-cat args) ""))
+ ((eq? color 'underline)
+ (cat "" (apply-cat args) ""))
+ (else
+ (let ((cname (if (number? color) (cat "#" color) color)))
+ (fmt-if fmt-use-html-font?
+ (cat "" (apply-cat args)
+ "")
+ (cat ""
+ (apply-cat args) "")))))
+ (lambda (st)
+ (let ((old-color (fmt-color st)))
+ ((fmt-let 'color color
+ (cat (ansi-escape color)
+ (apply-cat args)
+ (if (or (memv color '(bold underline))
+ (memv old-color '(bold underline)))
+ (ansi-escape 'reset)
+ (lambda (st) st))
+ (ansi-escape old-color)))
+ st)))))
+
+(define (fmt-red . args) (fmt-colored 'red (apply-cat args)))
+(define (fmt-blue . args) (fmt-colored 'blue (apply-cat args)))
+(define (fmt-green . args) (fmt-colored 'green (apply-cat args)))
+(define (fmt-cyan . args) (fmt-colored 'cyan (apply-cat args)))
+(define (fmt-yellow . args) (fmt-colored 'yellow (apply-cat args)))
+(define (fmt-magenta . args) (fmt-colored 'magenta (apply-cat args)))
+(define (fmt-white . args) (fmt-colored 'white (apply-cat args)))
+(define (fmt-black . args) (fmt-colored 'black (apply-cat args)))
+(define (fmt-bold . args) (fmt-colored 'bold (apply-cat args)))
+(define (fmt-underline . args) (fmt-colored 'underline (apply-cat args)))
+
diff --git a/functional-tests/fmt/fmt-column.scm b/functional-tests/fmt/fmt-column.scm
new file mode 100644
index 0000000..9ec4865
--- /dev/null
+++ b/functional-tests/fmt/fmt-column.scm
@@ -0,0 +1,417 @@
+;;;; fmt-block.scm -- columnar formatting
+;;
+;; Copyright (c) 2006-2011 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; Columnar formatting
+;;
+;; A line-oriented formatter. Takes a list of
+;; (line-fmt1 gen-fmt1 line-fmt2 gen-fmt2 ...)
+;; and formats each of the gen-fmt1 formats as columns, printed
+;; side-by-side, each line allowing post-processing done by line-fmt1
+;; (just use dsp if you want to display the lines verbatim).
+
+;; Continuations come to the rescue to make this work properly,
+;; letting us weave the output between different columns without
+;; needing to build up intermediate strings.
+
+(define (fmt-columns . ls)
+ (lambda (orig-st)
+ (call-with-current-continuation
+ (lambda (return)
+ (define (infinite? x)
+ (and (pair? x) (pair? (cdr x)) (pair? (cddr x)) (caddr x)))
+ (let ((q1 '())
+ (q2 '())
+ (remaining (length (remove infinite? ls))))
+ (define (enq! proc) (set! q2 (cons proc q2)))
+ (define (deq!) (let ((proc (car q1))) (set! q1 (cdr q1)) proc))
+ (define (line-init!) (set! q1 (reverse q2)) (set! q2 '()))
+ (define (line-done?) (null? q1))
+ (define line-buf '())
+ (define line-non-empty? #f)
+ (define (write-column fmt str finite?)
+ (set! line-buf (cons (cons fmt str) line-buf))
+ (if finite? (set! line-non-empty? #t)))
+ (define (write-line)
+ (cond
+ (line-non-empty?
+ (for-each
+ (lambda (x) (set! orig-st (((car x) (cdr x)) orig-st)))
+ (reverse line-buf))
+ (set! orig-st (nl orig-st))))
+ (set! line-buf '())
+ (set! line-non-empty? #f)
+ (line-init!))
+ (define (next cont)
+ (enq! cont)
+ (cond
+ ((line-done?)
+ (write-line)
+ (if (not (positive? remaining)) (finish) ((deq!) #f)))
+ (else ((deq!) #f))))
+ (define (finish)
+ (write-line)
+ (return orig-st))
+ (define (make-empty-col fmt)
+ (define (blank *ignored*)
+ (write-column fmt "" #f)
+ (next blank)) ; infinite loop, next terminates for us
+ blank)
+ (define (make-col st fmt gen finite?)
+ (let ((acc '())) ; buffer incomplete lines
+ (lambda (*ignored*)
+ (define (output* str st)
+ (let lp ((i 0))
+ (let ((nli (string-index str #\newline i)))
+ (cond
+ (nli
+ (let ((line
+ (string-concatenate-reverse
+ (cons (substring/shared str i nli) acc))))
+ (set! acc '())
+ (write-column fmt line finite?)
+ (call-with-current-continuation next)
+ (lp (+ nli 1))))
+ (else
+ (set! acc (cons (substring/shared str i) acc))))))
+ ;; update - don't output or the string port will fill up
+ (fmt-update str st))
+ ;; gen threads through it's own state, ignore result
+ (gen (fmt-set-writer! (copy-fmt-state st) output*))
+ ;; reduce # of remaining finite columns
+ (set! remaining (- remaining 1))
+ ;; write any remaining accumulated output
+ (if (pair? acc)
+ (let ((s (string-concatenate-reverse acc)))
+ (write-column fmt s (and finite? (not (equal? s ""))))))
+ ;; (maybe) loop with an empty column in place
+ (if (not (positive? remaining))
+ (finish)
+ (next (make-empty-col fmt))))))
+ ;; queue up the initial formatters
+ (for-each
+ (lambda (col)
+ (let ((st (fmt-set-port! (copy-fmt-state orig-st)
+ (open-output-string))))
+ (enq! (make-col st (car col) (dsp (cadr col))
+ (not (infinite? col))))))
+ ls)
+ (line-init!)
+ ;; start
+ ((deq!) #f))))))
+
+(define (columnar . ls)
+ (define (proportional-width? w)
+ (and (number? w)
+ (or (< 0 w 1)
+ (and (inexact? w) (= w 1.0)))))
+ (define (whitespace-pad? st)
+ (char-whitespace? (or (fmt-pad-char st) #\space)))
+ (define (build-column ls)
+ (let-optionals* ls ((fixed-width #f)
+ (width #f)
+ (last? #t)
+ (tail '())
+ (gen #f)
+ (prefix '())
+ (align 'left)
+ (infinite? #f))
+ (define (scale-width st)
+ (max 1 (inexact->exact
+ (truncate (* width (- (fmt-width st) fixed-width))))))
+ (define (padder)
+ (if (proportional-width? width)
+ (case align
+ ((right)
+ (lambda (str) (lambda (st) ((pad/left (scale-width st) str) st))))
+ ((center)
+ (lambda (str) (lambda (st) ((pad/both (scale-width st) str) st))))
+ (else
+ (lambda (str) (lambda (st) ((pad/right (scale-width st) str) st)))))
+ (case align
+ ((right) (lambda (str) (pad/left width str)))
+ ((center) (lambda (str) (pad/both width str)))
+ (else (lambda (str) (pad/right width str))))))
+ (define (affix x)
+ (cond
+ ((pair? tail)
+ (lambda (str)
+ (cat (string-concatenate prefix)
+ (x str)
+ (string-concatenate tail))))
+ ((pair? prefix)
+ (lambda (str) (cat (string-concatenate prefix) (x str))))
+ (else x)))
+ (list
+ ;; line formatter
+ (affix
+ (let ((pad (padder)))
+ (if (and last? (not (pair? tail)) (eq? align 'left))
+ (lambda (str)
+ (lambda (st)
+ (((if (whitespace-pad? st) dsp pad) str) st)))
+ pad)))
+ ;; generator
+ (if (proportional-width? width)
+ (lambda (st) ((with-width (scale-width st) gen) st))
+ (with-width width gen))
+ infinite?
+ )))
+ (define (adjust-widths ls border-width)
+ (let* ((fixed-ls
+ (filter (lambda (x) (and (number? (car x)) (>= (car x) 1))) ls))
+ (fixed-total (fold + border-width (map car fixed-ls)))
+ (scaled-ls (filter (lambda (x) (proportional-width? (car x))) ls))
+ (denom (- (length ls) (+ (length fixed-ls) (length scaled-ls))))
+ (rest (if (zero? denom)
+ 0
+ (exact->inexact
+ (/ (- 1 (fold + 0 (map car scaled-ls))) denom)))))
+ (if (negative? rest)
+ (error 'columnar "fractional widths must sum to less than 1"
+ (map car scaled-ls)))
+ (map
+ (lambda (col)
+ (cons fixed-total
+ (if (not (number? (car col))) (cons rest (cdr col)) col)))
+ ls)))
+ (define (finish ls border-width)
+ (apply fmt-columns
+ (map build-column (adjust-widths (reverse ls) border-width))))
+ (let lp ((ls ls) (strs '()) (align 'left) (infinite? #f)
+ (width #t) (border-width 0) (res '()))
+ (cond
+ ((null? ls)
+ (if (pair? strs)
+ (finish (cons (cons (caar res)
+ (cons #t (cons (append (reverse strs)
+ (caddar res))
+ (cdddar res))))
+ (cdr res))
+ border-width)
+ (finish (cons (cons (caar res) (cons #t (cddar res))) (cdr res))
+ border-width)))
+ ((string? (car ls))
+ (if (string-index (car ls) #\newline)
+ (error 'columnar "column string literals can't contain newlines")
+ (lp (cdr ls) (cons (car ls) strs) align infinite?
+ width (+ border-width (string-length (car ls))) res)))
+ ((number? (car ls))
+ (lp (cdr ls) strs align infinite? (car ls) border-width res))
+ ((eq? (car ls) 'infinite)
+ (lp (cdr ls) strs align #t width border-width res))
+ ((symbol? (car ls))
+ (lp (cdr ls) strs (car ls) infinite? width border-width res))
+ ((procedure? (car ls))
+ (lp (cdr ls) '() 'left #f #t border-width
+ (cons (list width #f '() (car ls) (reverse strs) align infinite?)
+ res)))
+ (else
+ (error 'columnar "invalid column" (car ls))))))
+
+(define (max-line-width string-width str)
+ (let lp ((i 0) (hi 0))
+ (let ((j (string-index str #\newline i)))
+ (if j
+ (lp (+ j 1) (max hi (string-width (substring str i j))))
+ (max hi (string-width (substring str i (string-length str))))))))
+
+(define (pad-finite st proc width)
+ (let* ((str ((fmt-to-string proc) (copy-fmt-state st)))
+ (w (max-line-width (or (fmt-string-width st) string-length) str)))
+ (list (cat str)
+ (if (and (integer? width) (exact? width))
+ (max width w)
+ w))))
+
+(define (tabular . ls)
+ (lambda (st)
+ (let lp ((ls ls) (infinite? #f) (width #t) (res '()))
+ (cond
+ ((null? ls)
+ ((apply columnar (reverse res)) st))
+ ((number? (car ls))
+ (lp (cdr ls) infinite? (car ls) res))
+ ((eq? 'infinite (car ls))
+ (lp (cdr ls) #t width (cons (car ls) res)))
+ ((procedure? (car ls))
+ (if infinite?
+ (if width
+ (lp (cdr ls) #f #t (cons (car ls) (cons width res)))
+ (lp (cdr ls) #f #t (cons (car ls) res)))
+ (let ((gen+width (pad-finite st (car ls) width)))
+ (lp (cdr ls) #f #t (append gen+width res)))))
+ (else
+ (lp (cdr ls) infinite? width (cons (car ls) res)))))))
+
+;; break lines only, don't fmt-join short lines or justify
+(define (fold-lines . ls)
+ (lambda (st)
+ (define output (fmt-writer st))
+ (define (kons-in-line str st)
+ (let ((len ((or (fmt-string-width st) string-length) str))
+ (space (- (fmt-width st) (fmt-col st))))
+ (cond
+ ((or (<= len space) (not (positive? space)))
+ (output str st))
+ (else
+ (kons-in-line
+ (substring/shared str space len)
+ (output nl-str
+ (output (substring/shared str 0 space) st)))))))
+ ((fmt-let
+ 'writer
+ (lambda (str st)
+ (let lp ((str str) (st st))
+ (let ((nli (string-index str #\newline)))
+ (cond
+ ((not nli)
+ (kons-in-line str st))
+ (else
+ (lp (substring/shared str (+ nli 1))
+ (output nl-str
+ (kons-in-line
+ (substring/shared str 0 nli)
+ st))))))))
+ (apply-cat ls))
+ st)))
+
+(define (wrap-fold-words seq knil max-width get-width line . o)
+ (let* ((last-line (if (pair? o) (car o) line))
+ (vec (if (list? seq) (list->vector seq) seq))
+ (len (vector-length vec))
+ (len-1 (- len 1))
+ (breaks (make-vector len #f))
+ (penalties (make-vector len #f))
+ (widths
+ (list->vector
+ (map get-width (if (list? seq) seq (vector->list vec))))))
+ (define (largest-fit i)
+ (let lp ((j (+ i 1)) (width (vector-ref widths i)))
+ (let ((width (+ width 1 (vector-ref widths j))))
+ (cond
+ ((>= width max-width) (- j 1))
+ ((>= j len-1) len-1)
+ (else (lp (+ j 1) width))))))
+ (define (min-penalty! i)
+ (cond
+ ((>= i len-1) 0)
+ ((vector-ref penalties i))
+ (else
+ (vector-set! penalties i (expt (+ max-width 1) 3))
+ (vector-set! breaks i i)
+ (let ((k (largest-fit i)))
+ (let lp ((j i) (width 0))
+ (if (<= j k)
+ (let* ((width (+ width (vector-ref widths j)))
+ (break-penalty
+ (+ (max 0 (expt (- max-width (+ width (- j i))) 3))
+ (min-penalty! (+ j 1)))))
+ (cond
+ ((< break-penalty (vector-ref penalties i))
+ (vector-set! breaks i j)
+ (vector-set! penalties i break-penalty)))
+ (lp (+ j 1) width)))))
+ (if (>= (vector-ref breaks i) len-1)
+ (vector-set! penalties i 0))
+ (vector-ref penalties i))))
+ (define (sub-list i j)
+ (let lp ((i i) (res '()))
+ (if (> i j)
+ (reverse res)
+ (lp (+ i 1) (cons (vector-ref vec i) res)))))
+ (cond
+ ((zero? len)
+ ;; degenerate case
+ (last-line '() knil))
+ (else
+ ;; compute optimum breaks
+ (vector-set! breaks len-1 len-1)
+ (vector-set! penalties len-1 0)
+ (min-penalty! 0)
+ ;; fold
+ (let lp ((i 0) (acc knil))
+ (let ((break (vector-ref breaks i)))
+ (if (>= break len-1)
+ (last-line (sub-list i len-1) acc)
+ (lp (+ break 1) (line (sub-list i break) acc)))))))))
+
+;; XXXX don't split, traverse the string manually and keep track of
+;; sentence endings so we can insert two spaces
+(define (wrap-fold str . o)
+ (apply wrap-fold-words (string-tokenize str) o))
+
+(define (wrap-lines . ls)
+ (define (print-line ls st)
+ (nl ((fmt-join dsp ls " ") st)))
+ (define buffer '())
+ (lambda (st)
+ ((fmt-let
+ 'writer
+ (lambda (str st) (set! buffer (cons str buffer)) st)
+ (apply-cat ls))
+ st)
+ (wrap-fold (string-concatenate-reverse buffer)
+ st (fmt-width st)
+ (or (fmt-string-width st) string-length)
+ print-line)))
+
+(define (justify . ls)
+ (lambda (st)
+ (let ((width (fmt-width st))
+ (string-width (or (fmt-string-width st) string-length))
+ (output (fmt-writer st))
+ (buffer '()))
+ (define (justify-line ls st)
+ (if (null? ls)
+ (nl st)
+ (let* ((sum (fold (lambda (s n) (+ n (string-width s))) 0 ls))
+ (len (length ls))
+ (diff (max 0 (- width sum)))
+ (sep (make-string (if (= len 1)
+ 0
+ (quotient diff (- len 1)))
+ #\space))
+ (rem (if (= len 1)
+ diff
+ (remainder diff (- len 1)))))
+ (output
+ (call-with-output-string
+ (lambda (p)
+ (display (car ls) p)
+ (let lp ((ls (cdr ls)) (i 1))
+ (cond
+ ((pair? ls)
+ (display sep p)
+ (if (<= i rem) (write-char #\space p))
+ (display (car ls) p)
+ (lp (cdr ls) (+ i 1)))))
+ (newline p)))
+ st))))
+ (define (justify-last ls st)
+ (nl ((fmt-join dsp ls " ") st)))
+ ((fmt-let
+ 'writer
+ (lambda (str st) (set! buffer (cons str buffer)) st)
+ (apply-cat ls))
+ st)
+ (wrap-fold (string-concatenate-reverse buffer)
+ st width string-width justify-line justify-last))))
+
+(define (fmt-file path)
+ (lambda (st)
+ (call-with-input-file path
+ (lambda (p)
+ (let lp ((st st))
+ (let ((line (read-line p)))
+ (if (eof-object? line)
+ st
+ (lp (nl ((dsp line) st))))))))))
+
+(define (line-numbers . o)
+ (let ((start (if (pair? o) (car o) 1)))
+ (fmt-join/range dsp start #f nl-str)))
+
diff --git a/functional-tests/fmt/fmt-gauche.scm b/functional-tests/fmt/fmt-gauche.scm
new file mode 100644
index 0000000..5f67162
--- /dev/null
+++ b/functional-tests/fmt/fmt-gauche.scm
@@ -0,0 +1,46 @@
+;;;; fmt-gauche.scm -- Gauche fmt extension
+;;
+;; Copyright (c) 2006-2011 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+(define-module text.fmt
+ (use srfi-1)
+ (use srfi-6)
+ (use srfi-13)
+ (export
+ new-fmt-state
+ fmt fmt-start fmt-if fmt-capture fmt-let fmt-bind fmt-null
+ fmt-ref fmt-set! fmt-add-properties! fmt-set-property!
+ fmt-col fmt-set-col! fmt-row fmt-set-row!
+ fmt-radix fmt-set-radix! fmt-precision fmt-set-precision!
+ fmt-properties fmt-set-properties! fmt-width fmt-set-width!
+ fmt-writer fmt-set-writer! fmt-port fmt-set-port!
+ fmt-decimal-sep fmt-set-decimal-sep!
+ fmt-file fmt-try-fit cat apply-cat nl fl nl-str
+ fmt-join fmt-join/last fmt-join/dot
+ fmt-join/prefix fmt-join/suffix fmt-join/range
+ pad pad/right pad/left pad/both trim trim/left trim/both trim/length
+ fit fit/left fit/both tab-to space-to wrt wrt/unshared dsp
+ pretty pretty/unshared slashified maybe-slashified
+ num num/si num/fit num/comma radix fix decimal-align ellipses
+ upcase downcase titlecase pad-char comma-char decimal-char
+ with-width wrap-lines fold-lines justify
+ make-string-fmt-transformer
+ make-space make-nl-space display-to-string write-to-string
+ fmt-columns columnar tabular line-numbers
+ ))
+(select-module text.fmt)
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;; SRFI-69 compatible hashtables
+
+(define (make-eq?-table)
+ (make-hash-table 'eq?))
+(define hash-table-ref/default hash-table-get)
+(define hash-table-set! hash-table-put!)
+(define (hash-table-walk tab proc) (hash-table-for-each tab proc))
+
+(define (mantissa+exponent num)
+ (let ((vec (decode-float num)))
+ (list (vector-ref vec 0) (vector-ref vec 1))))
+
diff --git a/functional-tests/fmt/fmt-js.scm b/functional-tests/fmt/fmt-js.scm
new file mode 100644
index 0000000..00a44f0
--- /dev/null
+++ b/functional-tests/fmt/fmt-js.scm
@@ -0,0 +1,74 @@
+;;;; fmt-js.scm -- javascript formatting utilities
+;;
+;; Copyright (c) 2011-2012 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+
+(define (js-expr x)
+ (fmt-let 'gen js-expr/sexp
+ (lambda (st) (((or (fmt-gen st) js-expr/sexp) x) st))))
+
+(define (js-expr/sexp x)
+ (cond
+ ((procedure? x)
+ x)
+ ((pair? x)
+ (case (car x)
+ ((%fun function) (apply js-function (cdr x)))
+ ((%var var) (apply js-var (cdr x)))
+ ((eq? ===) (apply js=== (cdr x)))
+ ((>>>) (apply js>>> (cdr x)))
+ ((%array) (js-array x))
+ ((%object) (js-object (cdr x)))
+ ((%comment) (js-comment x))
+ (else (c-expr/sexp x))))
+ ((vector? x) (js-array x))
+ ((boolean? x) (cat (if x "true" "false")))
+ ((char? x) (js-expr/sexp (string x)))
+ (else (c-expr/sexp x))))
+
+(define (js-function . x)
+ (let* ((name (and (symbol? (car x)) (car x)))
+ (params (if name (cadr x) (car x)))
+ (body (if name (cddr x) (cdr x))))
+ (c-block
+ (cat "function " (dsp (or name "")) "("
+ (fmt-join dsp params ", ") ")")
+ (fmt-let 'return? #t (c-in-stmt (apply c-begin body))))))
+
+(define (js-var . args)
+ (apply c-var 'var args))
+
+(define (js=== . args)
+ (apply c-op "===" args))
+
+(define (js>>> . args)
+ (apply c-op ">>>" args))
+
+(define (js-comment . args)
+ (columnar "// " (apply-cat args)))
+
+(define (js-array x)
+ (let ((ls (vector->list x)))
+ (c-wrap-stmt
+ (fmt-try-fit
+ (fmt-let 'no-wrap? #t (cat "[" (fmt-join js-expr ls ", ") "]"))
+ (lambda (st)
+ (let* ((col (fmt-col st))
+ (sep (string-append "," (make-nl-space col))))
+ ((cat "[" (fmt-join js-expr ls sep) "]" nl) st)))))))
+
+(define (js-pair x)
+ (cat (js-expr (car x)) ": " (js-expr (cdr x))))
+
+(define (js-object ls)
+ (c-in-expr
+ (fmt-try-fit
+ (fmt-let 'no-wrap? #t (cat "{" (fmt-join js-pair ls ", ") "}"))
+ (lambda (st)
+ (let* ((col (fmt-col st))
+ (sep (string-append "," (make-nl-space col))))
+ ((cat "{" (fmt-join js-pair ls sep) "}" nl) st))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
diff --git a/functional-tests/fmt/fmt-pretty.scm b/functional-tests/fmt/fmt-pretty.scm
new file mode 100644
index 0000000..3992084
--- /dev/null
+++ b/functional-tests/fmt/fmt-pretty.scm
@@ -0,0 +1,263 @@
+;;;; fmt-pretty.scm -- pretty printing format combinator
+;;
+;; Copyright (c) 2006-2007 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; additional settings
+
+(define (fmt-shares st) (fmt-ref st 'shares))
+(define (fmt-set-shares! st x) (fmt-set! st 'shares x))
+(define (fmt-copy-shares st)
+ (fmt-set-shares! (copy-fmt-state st) (copy-shares (fmt-shares st))))
+
+(define (copy-shares shares)
+ (let ((tab (make-eq?-table)))
+ (hash-table-walk
+ (car shares)
+ (lambda (obj x) (eq?-table-set! tab obj (cons (car x) (cdr x)))))
+ (cons tab (cdr shares))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; utilities
+
+(define (fmt-shared-write obj proc)
+ (lambda (st)
+ (let* ((shares (fmt-shares st))
+ (cell (and shares (eq?-table-ref (car shares) obj))))
+ (if (pair? cell)
+ (cond
+ ((cdr cell)
+ ((fmt-writer st) (gen-shared-ref (car cell) "#") st))
+ (else
+ (set-car! cell (cdr shares))
+ (set-cdr! cell #t)
+ (set-cdr! shares (+ (cdr shares) 1))
+ (proc ((fmt-writer st) (gen-shared-ref (car cell) "=") st))))
+ (proc st)))))
+
+(define (fmt-join/shares fmt ls . o)
+ (let ((sep (dsp (if (pair? o) (car o) " "))))
+ (lambda (st)
+ (if (null? ls)
+ st
+ (let* ((shares (fmt-shares st))
+ (tab (car shares))
+ (output (fmt-writer st)))
+ (let lp ((ls ls) (st st))
+ (let ((st ((fmt (car ls)) st))
+ (rest (cdr ls)))
+ (cond
+ ((null? rest) st)
+ ((pair? rest)
+ (call-with-shared-ref/cdr rest st shares
+ (lambda (st) (lp rest st))
+ sep))
+ (else ((fmt rest) (output ". " (sep st))))))))))))
+
+;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
+;;; pretty printing
+
+(define (non-app? x)
+ (if (pair? x)
+ (or (not (or (null? (cdr x)) (pair? (cdr x))))
+ (non-app? (car x)))
+ (not (symbol? x))))
+
+(define syntax-abbrevs
+ '((quote . "'") (quasiquote . "`") (unquote . ",") (unquote-splicing . ",@")
+ ))
+
+(define (pp-let ls)
+ (if (and (pair? (cdr ls)) (symbol? (cadr ls)))
+ (pp-with-indent 2 ls)
+ (pp-with-indent 1 ls)))
+
+(define indent-rules
+ `((lambda . 1) (define . 1)
+ (let . ,pp-let) (loop . ,pp-let)
+ (let* . 1) (letrec . 1) (letrec* . 1) (and-let* . 1) (let1 . 2)
+ (let-values . 1) (let*-values . 1) (receive . 2) (parameterize . 1)
+ (let-syntax . 1) (letrec-syntax . 1) (syntax-rules . 1) (syntax-case . 2)
+ (match . 1) (match-let . 1) (match-let* . 1)
+ (if . 3) (when . 1) (unless . 1) (case . 1) (while . 1) (until . 1)
+ (do . 2) (dotimes . 1) (dolist . 1) (test . 1)
+ (condition-case . 1) (guard . 1) (rec . 1)
+ (call-with-current-continuation . 0)
+ ))
+
+(define indent-prefix-rules
+ `(("with-" . -1) ("call-with-" . -1) ("define-" . 1))
+ )
+
+(define indent-suffix-rules
+ `(("-case" . 1))
+ )
+
+(define (pp-indentation form)
+ (let ((indent
+ (cond
+ ((assq (car form) indent-rules) => cdr)
+ ((and (symbol? (car form))
+ (let ((str (symbol->string (car form))))
+ (or (find (lambda (rx) (string-prefix? (car rx) str))
+ indent-prefix-rules)
+ (find (lambda (rx) (string-suffix? (car rx) str))
+ indent-suffix-rules))))
+ => cdr)
+ (else #f))))
+ (if (and (number? indent) (negative? indent))
+ (max 0 (- (+ (length+ form) indent) 1))
+ indent)))
+
+(define (pp-with-indent indent-rule ls)
+ (lambda (st)
+ (let* ((col1 (fmt-col st))
+ (st ((cat "(" (pp-object (car ls))) st))
+ (col2 (fmt-col st))
+ (fixed (take* (cdr ls) (or indent-rule 1)))
+ (tail (drop* (cdr ls) (or indent-rule 1)))
+ (st2 (fmt-copy-shares st))
+ (first-line
+ ((fmt-to-string (cat " " (fmt-join/shares pp-flat fixed " "))) st2))
+ (default
+ (let ((sep (make-nl-space (+ col1 1))))
+ (cat sep (fmt-join/shares pp-object (cdr ls) sep) ")"))))
+ (cond
+ ((< (+ col2 (string-length first-line)) (fmt-width st2))
+ ;; fixed values on first line
+ (let ((sep (make-nl-space
+ (if indent-rule (+ col1 2) (+ col2 1)))))
+ ((cat first-line
+ (cond
+ ((not (or (null? tail) (pair? tail)))
+ (cat ". " (pp-object tail)))
+ ((> (length+ (cdr ls)) (or indent-rule 1))
+ (cat sep (fmt-join/shares pp-object tail sep)))
+ (else
+ fmt-null))
+ ")")
+ st2)))
+ (indent-rule ;;(and indent-rule (not (pair? (car ls))))
+ ;; fixed values lined up, body indented two spaces
+ ((fmt-try-fit
+ (lambda (st)
+ ((cat
+ " "
+ (fmt-join/shares pp-object fixed (make-nl-space (+ col2 1)))
+ (if (pair? tail)
+ (let ((sep (make-nl-space (+ col1 2))))
+ (cat sep (fmt-join/shares pp-object tail sep)))
+ "")
+ ")")
+ (fmt-copy-shares st)))
+ default)
+ st))
+ (else
+ ;; all on separate lines
+ (default st))))))
+
+(define (pp-app ls)
+ (let ((indent-rule (pp-indentation ls)))
+ (if (procedure? indent-rule)
+ (indent-rule ls)
+ (pp-with-indent indent-rule ls))))
+
+;; the elements may be shared, just checking the top level list
+;; structure
+(define (proper-non-shared-list? ls shares)
+ (let ((tab (car shares)))
+ (let lp ((ls ls))
+ (or (null? ls)
+ (and (pair? ls)
+ (not (eq?-table-ref tab ls))
+ (lp (cdr ls)))))))
+
+(define (pp-flat x)
+ (cond
+ ((pair? x)
+ (fmt-shared-write
+ x
+ (cond
+ ((and (pair? (cdr x)) (null? (cddr x))
+ (assq (car x) syntax-abbrevs))
+ => (lambda (abbrev)
+ (cat (cdr abbrev) (pp-flat (cadr x)))))
+ (else
+ (cat "(" (fmt-join/shares pp-flat x " ") ")")))))
+ ((vector? x)
+ (fmt-shared-write
+ x
+ (cat "#(" (fmt-join/shares pp-flat (vector->list x) " ") ")")))
+ (else
+ (lambda (st) ((write-with-shares x (fmt-shares st)) st)))))
+
+(define (pp-pair ls)
+ (fmt-shared-write
+ ls
+ (cond
+ ;; one element list, no lines to break
+ ((null? (cdr ls))
+ (cat "(" (pp-object (car ls)) ")"))
+ ;; quote or other abbrev
+ ((and (pair? (cdr ls)) (null? (cddr ls))
+ (assq (car ls) syntax-abbrevs))
+ => (lambda (abbrev)
+ (cat (cdr abbrev) (pp-object (cadr ls)))))
+ (else
+ (fmt-try-fit
+ (lambda (st) ((pp-flat ls) (fmt-copy-shares st)))
+ (lambda (st)
+ (if (and (non-app? ls)
+ (proper-non-shared-list? ls (fmt-shares st)))
+ ((pp-data-list ls) st)
+ ((pp-app ls) st))))))))
+
+(define (pp-data-list ls)
+ (lambda (st)
+ (let* ((output (fmt-writer st))
+ (st (output "(" st))
+ (col (fmt-col st))
+ (width (- (fmt-width st) col))
+ (st2 (fmt-copy-shares st)))
+ (cond
+ ((and (pair? (cdr ls)) (pair? (cddr ls)) (pair? (cdddr ls))
+ ((fits-in-columns ls pp-flat width) st2))
+ => (lambda (ls)
+ ;; at least four elements which can be broken into columns
+ (let* ((prefix (make-nl-space (+ col 1)))
+ (widest (+ 1 (car ls)))
+ (columns (quotient width widest))) ; always >= 2
+ (let lp ((ls (cdr ls)) (st st2) (i 1))
+ (cond
+ ((null? ls)
+ (output ")" st))
+ ((null? (cdr ls))
+ (output ")" (output (car ls) st)))
+ (else
+ (let ((st (output (car ls) st)))
+ (if (>= i columns)
+ (lp (cdr ls) (output prefix st) 1)
+ (let* ((pad (- widest (string-length (car ls))))
+ (st (output (make-space pad) st)))
+ (lp (cdr ls) st (+ i 1)))))))))))
+ (else
+ ;; no room, print one per line
+ ((cat (fmt-join pp-object ls (make-nl-space col)) ")") st))))))
+
+(define (pp-vector vec)
+ (fmt-shared-write vec (cat "#" (pp-data-list (vector->list vec)))))
+
+(define (pp-object obj)
+ (cond
+ ((pair? obj) (pp-pair obj))
+ ((vector? obj) (pp-vector obj))
+ (else (lambda (st) ((write-with-shares obj (fmt-shares st)) st)))))
+
+(define (pretty obj)
+ (fmt-bind 'shares (cons (make-shared-ref-table obj) 0)
+ (cat (pp-object obj) fl)))
+
+(define (pretty/unshared obj)
+ (fmt-bind 'shares (cons (make-eq?-table) 0) (cat (pp-object obj) fl)))
+
diff --git a/functional-tests/fmt/fmt-unicode.scm b/functional-tests/fmt/fmt-unicode.scm
new file mode 100644
index 0000000..b3e9f91
--- /dev/null
+++ b/functional-tests/fmt/fmt-unicode.scm
@@ -0,0 +1,135 @@
+;;;; fmt-unicode.scm -- Unicode character width and ANSI escape support
+;;
+;; Copyright (c) 2006-2007 Alex Shinn. All rights reserved.
+;; BSD-style license: http://synthcode.com/license.txt
+
+;; a condensed non-spacing mark range from UnicodeData.txt (chars with
+;; the Mn property) - generated partially by hand, should automate
+;; this better
+
+(define low-non-spacing-chars
+ (bytevector
+#xff #xff #xff #xff #xff #xff #xff #xff #xff #xff #xff #xff #xff #xff 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+#x78 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 #xfe #xff #xff #xff #xff #xff #x1f 0 0 0 0 0 0 0
+ 0 0 #x3f 0 0 0 0 0 0 #xf8 #xff #x01 0 0 #x01 0
+ 0 0 0 0 0 0 0 0 0 0 #xc0 #xff #xff #x3f 0 0
+ 0 0 #x02 0 0 0 #xff #xff #xff #x07 0 0 0 0 0 0
+ 0 0 0 0 #xc0 #xff #x01 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+#x06 0 0 0 0 0 0 #x10 #xfe #x21 #x1e 0 #x0c 0 0 0
+#x02 0 0 0 0 0 0 #x10 #x1e #x20 0 0 #x0c 0 0 0
+#x06 0 0 0 0 0 0 #x10 #xfe #x3f 0 0 0 0 #x03 0
+#x06 0 0 0 0 0 0 #x30 #xfe #x21 0 0 #x0c 0 0 0
+#x02 0 0 0 0 0 0 #x90 #x0e #x20 #x40 0 0 0 0 0
+#x04 0 0 0 0 0 0 0 0 #x20 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 #xc0 #xc1 #xff #x7f 0 0 0 0 0
+ 0 0 0 0 0 0 0 #x10 #x40 #x30 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 #x0e #x20 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 #x04 #x7c 0 0 0 0 0
+ 0 0 0 0 0 0 #xf2 #x07 #x80 #x7f 0 0 0 0 0 0
+ 0 0 0 0 0 0 #xf2 #x1f 0 #x3f 0 0 0 0 0 0
+ 0 0 0 #x03 0 0 #xa0 #x02 0 0 0 0 0 0 #xfe #x7f
+#xdf 0 #xff #xff #xff #xff #xff #x1f #x40 0 0 0 0 0 0 0
+ 0 0 0 0 0 #xe0 #xfd #x02 0 0 0 #x03 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 #x1c 0 0 0 #x1c 0 0 0 #x0c 0 0 0 #x0c 0
+ 0 0 0 0 0 0 #x80 #x3f #x40 #xfe #x0f #x20 0 0 0 0
+ 0 #x38 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 #x02 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 #x87 #x01 #x04 #x0e 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0
+ 0 0 0 0 0 0 0 0 0 0 #xff #x1f #xe2 #x07
+ ))
+
+(define (unicode-char-width c)
+ (let ((ci (char->integer c)))
+ (cond
+ ;; hand-checked ranges from EastAsianWidth.txt
+ ((<= #x1100 ci #x115F) 2) ; Hangul
+ ((<= #x2E80 ci #x4DB5) 2) ; CJK
+ ((<= #x4E00 ci #xA4C6) 2)
+ ((<= #xAC00 ci #xD7A3) 2) ; Hangul
+ ((<= #xF900 ci #xFAD9) 2) ; CJK compat
+ ((<= #xFE10 ci #xFE6B) 2)
+ ((<= #xFF01 ci #xFF60) 2)
+ ((<= #xFFE0 ci #xFFE6) 2)
+ ((<= #x20000 ci #x30000) 2)
+ ;; non-spacing mark (Mn) ranges from UnicodeData.txt
+ ((<= #x0300 ci #x3029)
+ ;; inlined bit-vector-ref for portability
+ (let* ((i (- ci #x0300))
+ (byte (quotient i 8))
+ (off (remainder i 8)))
+ (if (zero? (bitwise-and (bytevector-u8-ref low-non-spacing-chars byte)
+ (bitwise-arithmetic-shift 1 off)))
+ 1
+ 0)))
+ ((<= #x302A ci #x302F) 0)
+ ((<= #x3099 ci #x309A) 0)
+ ((= #xFB1E ci) 0)
+ ((<= #xFE00 ci #xFE23) 0)
+ ((<= #x1D167 ci #x1D169) 0)
+ ((<= #x1D17B ci #x1D182) 0)
+ ((<= #x1D185 ci #x1D18B) 0)
+ ((<= #x1D1AA ci #x1D1AD) 0)
+ ((<= #xE0100 ci #xE01EF) 0)
+ (else 1))))
+
+(define (unicode-string-width str . o)
+ (let ((start (if (pair? o) (car o) 0))
+ (end (if (and (pair? o) (pair? (cdr o)))
+ (cadr o)
+ (string-length str))))
+ (let lp1 ((i start) (width 0))
+ (if (>= i end)
+ width
+ (let ((c (string-ref str i)))
+ (cond
+ ;; ANSI escapes
+ ((and (= 27 (char->integer c)) ; esc
+ (< (+ i 1) end)
+ (eqv? #\[ (string-ref str (+ i 1))))
+ (let lp2 ((i (+ i 2)))
+ (cond ((>= i end) width)
+ ((memv (string-ref str i) '(#\m #\newline))
+ (lp1 (+ i 1) width))
+ (else (lp2 (+ i 1))))))
+ ;; unicode characters
+ ((>= (char->integer c) #x80)
+ (lp1 (+ i 1) (+ width (unicode-char-width c))))
+ ;; normal ASCII
+ (else (lp1 (+ i 1) (+ width 1)))))))))
+
+(define (fmt-unicode . args)
+ (fmt-let 'string-width unicode-string-width (apply-cat args)))
+
diff --git a/functional-tests/fmt/fmt.css b/functional-tests/fmt/fmt.css
new file mode 100644
index 0000000..616c419
--- /dev/null
+++ b/functional-tests/fmt/fmt.css
@@ -0,0 +1,69 @@
+body {
+color: black;
+background-color: white;
+margin-top: 2em;
+margin-left: 10%;
+width: 400pt;
+}
+
+pre {
+ background-color: beige;
+}
+
+pre.scheme {
+ background-color: white;
+}
+
+.subject {
+}
+
+h1 {
+margin-left: -5%;
+margin-top: 2em;
+font-size: large;
+}
+
+h2 {
+margin-left: -4%;
+margin-top: 1em;
+font-size: large;
+}
+
+h3,h4,h5,h6 {
+margin-left: -3%;
+margin-top: .5em;
+font-size: small;
+}
+
+.navigation {
+color: red;
+background-color: beige;
+text-align: right;
+font-style: italic;
+}
+
+
+.scheme {
+color: brown;
+}
+
+.scheme .keyword {
+color: #cc0000;
+font-weight: bold;
+}
+
+.scheme .variable {
+color: navy;
+}
+
+.scheme .global {
+color: purple;
+}
+
+.scheme .constant,.number,.char,.string,.boolean {
+color: green;
+}
+
+.scheme .comment {
+color: teal;
+}
diff --git a/functional-tests/fmt/fmt.doc b/functional-tests/fmt/fmt.doc
new file mode 100644
index 0000000..a821f2b
--- /dev/null
+++ b/functional-tests/fmt/fmt.doc
@@ -0,0 +1,1349 @@
+
+\title{Combinator Formatting}
+
+\eval
+(begin
+ (display "\n"))
+
+\flushright{\urlh{http://synthcode.com/}{Alex Shinn}}
+\flushright{\urlh{http://synthcode.com/scheme/fmt/fmt-0.8.4.tar.gz}{Download Version 0.8.4}}
+
+\eval(display "
\n\n")
+
+A library of procedures for formatting Scheme objects to text in
+various ways, and for easily concatenating, composing and extending
+these formatters efficiently without resorting to capturing and
+manipulating intermediate strings.
+
+\eval(display "
\n\n")
+
+\section{Table of Contents}
+
+\eval(display "\n\n\n\n")
+
+\eval(display "
\n\n")
+
+\section{Installation}
+
+Available for Chicken as the \p{fmt} egg, providing the \q{fmt},
+\q{fmt-c}, \q{fmt-color} and \q{fmt-unicode} extensions. To install
+manually for Chicken just run \p{"chicken-setup"} in the fmt
+directory.
+
+For Gauche run \p{"make gauche && make install-gauche"}. The modules
+are installed as \q{text.fmt}, \q{text.fmt.c}, \q{text.fmt.color} and
+\q{text.fmt.unicode}.
+
+For MzScheme you can download and install the latest \p{fmt.plt} yourself
+from:
+
+ \urlh{http://synthcode.com/scheme/fmt/fmt.plt}{http://synthcode.com/scheme/fmt/fmt.plt}
+
+To build the \p{fmt.plt} for yourself you can run \p{"make mzscheme"}.
+
+For Scheme48 the package descriptions are in \p{fmt-scheme48.scm}:
+
+\q{
+> ,config ,load fmt-scheme48.scm
+> ,open fmt
+}
+
+For other implementations you'll need to load SRFI's 1, 6, 13, 33
+(sample provided) and 69 (also provided), and then load the following
+files:
+
+\q{
+ (load "let-optionals.scm") ; if you don't have LET-OPTIONALS*
+ (load "read-line.scm") ; if you don't have READ-LINE
+ (load "string-ports.scm") ; if you don't have CALL-WITH-OUTPUT-STRING
+ (load "make-eq-table.scm")
+ (load "mantissa.scm")
+ (load "fmt.scm")
+ (load "fmt-pretty.scm") ; optional pretty printing
+ (load "fmt-column.scm") ; optional columnar output
+ (load "fmt-c.scm") ; optional C formatting utilities
+ (load "fmt-color.scm") ; optional color utilities
+ (load "fmt-unicode.scm") ; optional Unicode-aware formatting,
+ ; also requires SRFI-4 or SRFI-66
+}
+
+\section{Background}
+
+There are several approaches to text formatting. Building strings to
+\q{display} is not acceptable, since it doesn't scale to very large
+output. The simplest realistic idea, and what people resort to in
+typical portable Scheme, is to interleave \q{display} and \q{write}
+and manual loops, but this is both extremely verbose and doesn't
+compose well. A simple concept such as padding space can't be
+achieved directly without somehow capturing intermediate output.
+
+The traditional approach is to use templates - typically strings,
+though in theory any object could be used and indeed Emacs' mode-line
+format templates allow arbitrary sexps. Templates can use either
+escape sequences (as in C's \q{printf} and \urlh{#BIBITEM_2}{CL's}
+\q{format}) or pattern matching (as in Visual Basic's \q{Format},
+\urlh{#BIBITEM_6}{Perl6's} \q{form}, and SQL date formats). The
+primary disadvantage of templates is the relative difficulty (usually
+impossibility) of extending them, their opaqueness, and the
+unreadability that arises with complex formats. Templates are not
+without their advantages, but they are already addressed by other
+libraries such as \urlh{#BIBITEM_3}{SRFI-28} and
+\urlh{#BIBITEM_4}{SRFI-48}.
+
+This library takes a combinator approach. Formats are nested chains
+of closures, which are called to produce their output as needed.
+The primary goal of this library is to have, first and foremost, a
+maximally expressive and extensible formatting library. The next
+most important goal is scalability - to be able to handle
+arbitrarily large output and not build intermediate results except
+where necessary. The third goal is brevity and ease of use.
+
+\section{Usage}
+
+The primary interface is the \q{fmt} procedure:
+
+ \q{(fmt ...)}
+
+where \q{} has the same semantics as with \q{format} -
+specifically it can be an output-port, \q{#t} to indicate the current
+output port, or \q{#f} to accumulate output into a string.
+
+Each \q{} should be a format closure as discussed below. As a
+convenience, non-procedure arguments are also allowed and are
+formatted similar to \q{display}, so that
+
+ \q{(fmt #f "Result: " res nl)}
+
+would return the string \q{"Result: 42\n"}, assuming \q{RES} is bound
+to \q{42}.
+
+\q{nl} is the newline format combinator.
+
+\section{Specification}
+
+The procedure names have gone through several variations, and I'm
+still open to new suggestions. The current approach is to use
+abbreviated forms of standard output procedures when defining an
+equivalent format combinator (thus \q{display} becomes \q{dsp} and
+\q{write} becomes \q{wrt}), and to use an \q{fmt-} prefix for
+utilities and less common combinators. Variants of the same formatter
+get a \q{/} suffix.
+
+\subsection{Formatting Objects}
+
+\subsubsection*{(dsp )}
+
+Outputs \q{} using \q{display} semantics. Specifically, strings
+are output without surrounding quotes or escaping and characters are
+written as if by \q{write-char}. Other objects are written as with
+\q{write} (including nested strings and chars inside \q{}). This
+is the default behavior for top-level formats in \q{fmt}, \q{cat} and
+most other higher-order combinators.
+
+\subsubsection*{(wrt )}
+
+Outputs \q{} using \q{write} semantics. Handles shared
+structures as in \urlh{#BIBITEM_5}{SRFI-38}.
+
+\subsubsection*{(wrt/unshared )}
+
+As above, but doesn't handle shared structures. Infinite loops can
+still be avoided if used inside a combinator that truncates data (see
+\q{trim} and \q{fit} below).
+
+\subsubsection*{(pretty )}
+
+Pretty-prints \q{}. Also handles shared structures. Unlike many
+other pretty printers, vectors and data lists (lists that don't begin
+with a (nested) symbol), are printed in tabular format when there's
+room, greatly saving vertical space.
+
+\subsubsection*{(pretty/unshared )}
+
+As above but without sharing.
+
+\subsubsection*{(slashified [ ])}
+
+Outputs the string \q{}, escaping any quote or escape characters.
+If \q{} is \q{#f} escapes only the \q{} by
+doubling it, as in SQL strings and CSV values. If \q{} is
+provided, it should be a procedure of one character which maps that
+character to its escape value, e.g. \q{#\newline => #\n}, or \q{#f} if
+there is no escape value.
+
+ \q{(fmt #f (slashified "hi, \"bob!\""))}
+
+ \q{=> "hi, \"bob!\""}
+
+\subsubsection*{(maybe-slashified [ ])}
+
+Like \q{slashified}, but first checks if any quoting is required (by
+the existence of either any quote or escape characters, or any
+character matching \q{}), and if so outputs the string in quotes
+and with escapes. Otherwise outputs the string as is.
+
+ \q{(fmt #f (maybe-slashified "foo" char-whitespace? #\"))}
+
+ \q{=> "foo"}
+
+ \q{(fmt #f (maybe-slashified "foo bar" char-whitespace? #\"))}
+
+ \q{=> "\"foo bar\""}
+
+ \q{(fmt #f (maybe-slashified "foo\"bar\"baz" char-whitespace? #\"))}
+
+ \q{=> "\"foo\"bar\"baz\""}
+
+\subsection{Formatting Numbers}
+
+\subsubsection*{(num [ ])}
+
+Formats a single number \q{}. You can optionally specify any
+\q{} from 2 to 36 (even if \q{} isn't an integer).
+\q{} forces a fixed-point format.
+
+A \q{} of \q{#t} indicates to output a plus sign (+) for positive
+integers. However, if \q{} is a character, it means to wrap the
+number with that character and its mirror opposite if the number is
+negative. For example, \q{#\(} prints negative numbers in parenthesis,
+financial style: \q{-3.14 => (3.14)}
+
+\q{} is an integer specifying the number of digits between
+commas. Variable length, as in subcontinental-style, is not yet
+supported.
+
+\q{} is the character to use for commas, defaulting to \q{#\,}.
+
+\q{} is the character to use for decimals, defaulting to
+\q{#\.}, or to \q{#\,} (European style) if \q{} is already
+\q{#\.}.
+
+These parameters may seem unwieldy, but they can also take their
+defaults from state variables, described below.
+
+\subsubsection*{(num/comma [ ])}
+
+Shortcut for \q{num} to print with commas.
+
+ \q{(fmt #f (num/comma 1234567))}
+
+ \q{=> "1,234,567"}
+
+\subsubsection*{(num/si [ ])}
+
+Abbreviates \q{} with an SI suffix as in the -h or --si option to
+many GNU commands. The base defaults to 1024, using suffix names
+like Ki, Mi, Gi, etc. Other bases (e.g. the standard 1000) have the
+suffixes k, M, G, etc.
+
+The \q{} argument is appended only if an abbreviation is used.
+
+ \q{(fmt #f (num/si 608))}
+
+ \q{=> "608"}
+
+ \q{(fmt #f (num/si 3986))}
+
+ \q{=> "3.9Ki"}
+
+ \q{(fmt #f (num/si 3986 1000 "B"))}
+
+ \q{=> "4kB"}
+
+See \urlh{http://www.bipm.org/en/si/si_brochure/chapter3/prefixes.html}{http://www.bipm.org/en/si/si_brochure/chapter3/prefixes.html}.
+
+\subsubsection*{(num/fit . )}
+
+Like \q{num}, but if the result doesn't fit in \q{}, output
+instead a string of hashes (with the current \q{}) rather
+than showing an incorrectly truncated number. For example
+
+ \q{(fmt #f (fix 2 (num/fit 4 12.345)))}
+ \q{=> "#.##"}
+
+\subsubsection*{(num/roman )}
+
+Formats the number as a Roman numeral:
+
+ \q{(fmt #f (num/roman 1989))}
+ \q{=> "MCMLXXXIX"}
+
+\subsubsection*{(num/old-roman )}
+
+Formats the number as an old-style Roman numeral, without the
+subtraction abbreviation rule:
+
+ \q{(fmt #f (num/old-roman 1989))}
+ \q{=> "MDCCCCLXXXVIIII"}
+
+
+\subsection{Formatting Space}
+
+\subsubsection*{nl}
+
+Outputs a newline.
+
+\subsubsection*{fl}
+
+Short for "fresh line," outputs a newline only if we're not already
+at the start of a line.
+
+\subsubsection*{(space-to )}
+
+Outputs spaces up to the given \q{}. If the current column is
+already >= \q{}, does nothing.
+
+\subsubsection*{(tab-to [])}
+
+Outputs spaces up to the next tab stop, using tab stops of width
+\q{}, which defaults to 8. If already on a tab stop, does
+nothing. If you want to ensure you always tab at least one space, you
+can use \q{(cat " " (tab-to width))}.
+
+\subsubsection*{fmt-null}
+
+Outputs nothing (useful in combinators and as a default noop in
+conditionals).
+
+
+\subsection{Concatenation}
+
+\subsubsection*{(cat ...)}
+
+Concatenates the output of each \q{}.
+
+\subsubsection*{(apply-cat )}
+
+Equivalent to \q{(apply cat )} but may be more efficient.
+
+\subsubsection*{(fmt-join [])}
+
+Formats each element \q{} of \q{} with \q{(
+)}, inserting \q{} in between. \q{} defaults to the
+empty string, but can be any format.
+
+ \q{(fmt #f (fmt-join dsp '(a b c) ", "))}
+
+ \q{=> "a, b, c"}
+
+\subsubsection*{(fmt-join/prefix [])}
+\subsubsection*{(fmt-join/suffix [])}
+
+ \q{(fmt #f (fmt-join/prefix dsp '(usr local bin) "/"))}
+
+ \q{=> "/usr/local/bin"}
+
+As \q{fmt-join}, but inserts \q{} before/after every element.
+
+\subsubsection*{(fmt-join/last [])}
+
+As \q{fmt-join}, but the last element of the list is formatted with
+\q{} instead.
+
+\subsubsection*{(fmt-join/dot [])}
+
+As \q{fmt-join}, but if the list is a dotted list, then formats the dotted
+value with \q{} instead.
+
+
+\subsection{Padding and Trimming}
+
+\subsubsection*{(pad ...)}
+\subsubsection*{(pad/left ...)}
+\subsubsection*{(pad/both ...)}
+
+Analogs of SRFI-13 \q{string-pad}, these add extra space to the left,
+right or both sides of the output generated by the \q{}s to
+pad it to \q{}. If \q{} is exceeded has no effect.
+\q{pad/both} will include an extra space on the right side of the
+output if the difference is odd.
+
+\q{pad} does not accumulate any intermediate data.
+
+Note these are column-oriented padders, so won't necessarily work
+with multi-line output (padding doesn't seem a likely operation for
+multi-line output).
+
+\subsubsection*{(trim ...)}
+\subsubsection*{(trim/left ...)}
+\subsubsection*{(trim/both ...)}
+
+Analogs of SRFI-13 \q{string-trim}, truncates the output of the
+\q{}s to force it in under \q{} columns. As soon as
+any of the \q{}s exceed \q{}, stop formatting and
+truncate the result, returning control to whoever called \q{trim}. If
+\q{} is not exceeded has no effect.
+
+If a truncation ellipse is set (e.g. with the \q{ellipses} procedure
+below), then when any truncation occurs \q{trim} and \q{trim/left}
+will append and prepend the ellipse, respectively. \q{trim/both} will
+both prepend and append. The length of the ellipse will be considered
+when truncating the original string, so that the total width will
+never be longer than \q{}.
+
+ \q{(fmt #f (ellipses "..." (trim 5 "abcde")))}
+
+ \q{=> "abcde"}
+
+ \q{(fmt #f (ellipses "..." (trim 5 "abcdef")))}
+
+ \q{=> "ab..."}
+
+\subsubsection*{(trim/length ...)}
+
+A variant of \q{trim} which acts on the actual character count rather
+than columns, useful for truncating potentially cyclic data.
+
+\subsubsection*{(fit ...)}
+\subsubsection*{(fit/left ...)}
+\subsubsection*{(fit/both ...)}
+
+A combination of \q{pad} and \q{trunc}, ensures the output width is
+exactly \q{}, truncating if it goes over and padding if it goes
+under.
+
+
+\subsection{Format Variables}
+
+You may have noticed many of the formatters are aware of the current
+column. This is because each combinator is actually a procedure of
+one argument, the current format state, which holds basic
+information such as the row, column, and any other information that
+a format combinator may want to keep track of. The basic interface
+is:
+
+\subsubsection*{(fmt-let ...)}
+\subsubsection*{(fmt-bind ...)}
+
+\q{fmt-let} sets the name for the duration of the \q{}s, and
+restores it on return. \q{fmt-bind} sets it without restoring it.
+
+A convenience control structure can be useful in combination with
+these states:
+
+\subsubsection*{(fmt-if [])}
+
+\q{} takes one argument (the format state) and returns a boolean
+result. If true, the \q{} format is applied to the state,
+otherwise \q{} (defaulting to the identity) is applied.
+
+Many of the previously mentioned combinators have behavior which can
+be altered with state variables. Although \q{fmt-let} and \q{fmt-bind}
+could be used, these common variables have shortcuts:
+
+\subsubsection*{(radix ...)}
+\subsubsection*{(fix ...)}
+
+These alter the radix and fixed point precision of numbers output with
+\q{dsp}, \q{wrt}, \q{pretty} or \q{num}. These settings apply
+recursively to all output data structures, so that
+
+ \q{(fmt #f (radix 16 '(70 80 90)))}
+
+will return the string \q{"(#x46 #x50 #x5a)"}. Note that read/write
+invariance is essential, so for \q{dsp}, \q{wrt} and \q{pretty} the
+radix prefix is always included when not decimal. Use \q{num} if you
+want to format numbers in alternate bases without this prefix. For
+example,
+
+ \q{(fmt #f (radix 16 "(" (fmt-join num '(70 80 90) " ") ")"))}
+
+would return \q{"(46 50 5a)"}, the same output as above without the
+"#x" radix prefix.
+
+Note that fixed point formatting supports arbitrary precision in
+implementations with exact non-integral rationals. When trying to
+print inexact numbers more than the machine precision you will
+typically get results like
+
+ \q{(fmt #f (fix 30 #i2/3))}
+
+ \q{=> "0.666666666666666600000000000000"}
+
+but with an exact rational it will give you as many digits as you
+request:
+
+ \q{(fmt #f (fix 30 2/3))}
+
+ \q{=> "0.666666666666666666666666666667"}
+
+\subsubsection*{(decimal-align ...)}
+
+Specifies an alignment for the decimal place when formatting numbers,
+useful for outputting tables of numbers.
+
+\q{
+ (define (print-angles x)
+ (fmt-join num (list x (sin x) (cos x) (tan x)) " "))
+
+ (fmt #t (decimal-align 5 (fix 3 (fmt-join/suffix print-angles (iota 5) nl))))
+}
+
+would output
+
+\p{
+ 0.000 0.000 1.000 0.000
+ 1.000 0.842 0.540 1.557
+ 2.000 0.909 -0.416 -2.185
+ 3.000 0.141 -0.990 -0.142
+ 4.000 -0.757 -0.654 1.158
+}
+
+\subsubsection*{(comma-char ...)}
+\subsubsection*{(decimal-char ...)}
+
+\q{comma-char} and \q{decimal-char} set the defaults for number
+formatting.
+
+\subsubsection*{(pad-char ...)}
+
+The \q{pad-char} sets the character used by \q{space-to}, \q{tab-to},
+\q{pad/*}, and \q{fit/*}, and defaults to \q{#\space}.
+
+\q{
+ (define (print-table-of-contents alist)
+ (define (print-line x)
+ (cat (car x) (space-to 72) (pad/left 3 (cdr x))))
+ (fmt #t (pad-char #\. (fmt-join/suffix print-line alist nl))))
+
+ (print-table-of-contents
+ '(("An Unexpected Party" . 29)
+ ("Roast Mutton" . 60)
+ ("A Short Rest" . 87)
+ ("Over Hill and Under Hill" . 100)
+ ("Riddles in the Dark" . 115)))
+}
+
+would output
+
+\p{
+ An Unexpected Party.....................................................29
+ Roast Mutton............................................................60
+ A Short Rest............................................................87
+ Over Hill and Under Hill...............................................100
+ Riddles in the Dark....................................................115
+}
+
+\subsubsection*{(ellipse ...)}
+
+Sets the truncation ellipse to \q{}, would should be a string or
+character.
+
+\subsubsection*{(with-width ...)}
+
+Sets the maximum column width used by some formatters. The default
+is 78.
+
+
+\subsection{Columnar Formatting}
+
+Although \q{tab-to}, \q{space-to} and padding can be used to manually
+align columns to produce table-like output, these can be awkward to
+use. The optional extensions in this section make this easier.
+
+\subsubsection*{(columnar ...)}
+
+Formats each \q{} side-by-side, i.e. as though each were
+formatted separately and then the individual lines concatenated
+together. The current column width is divided evenly among the
+columns, and all but the last column are right-padded. For example
+
+ \q{(fmt #t (columnar (dsp "abc\\ndef\\n") (dsp "123\\n456\\n")))}
+
+outputs
+
+\p{
+ 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 \q{columnar} treats raw
+strings as literals inserted into the given location on every line, to
+be used as borders, for example:
+
+\q{
+ (fmt #t (columnar "/* " (dsp "abc\\ndef\\n")
+ " | " (dsp "123\\n456\\n")
+ " */"))
+}
+
+would output
+
+\p{
+ /* abc | 123 */
+ /* def | 456 */
+}
+
+You may also prefix any column with any of the symbols \q{'left},
+\q{'right} or \q{'center} to control the justification. The symbol
+\q{'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 \q{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 \q{yes(1)} command,
+etc.
+
+As an implementation detail, \q{columnar} uses first-class
+continuations to interleave the column output. The core \q{fmt}
+itself has no knowledge of or special support for \q{columnar}, which
+could complicate and potentially slow down simpler \q{fmt} operations.
+This is a testament to the power of \q{call/cc} - it can be used to
+implement coroutines or arbitrary control structures even where they
+were not planned for.
+
+\subsubsection*{(tabular ...)}
+
+Equivalent to \q{columnar} except that each column is padded at least
+to the minimum width required on any of its lines. Thus
+
+ \q{(fmt #t (tabular "|" (dsp "a\\nbc\\ndef\\n") "|" (dsp "123\\n45\\n6\\n") "|"))}
+
+outputs
+
+\p{
+|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, \q{tabular} cannot
+format a table larger than would fit in memory.
+
+\subsubsection*{(fmt-columns ...)}
+
+The low-level formatter on which \q{columnar} is based. Each \q{}
+must be a list of 2-3 elements:
+
+ \q{( [])}
+
+where \q{} is the column generator as above, and the
+\q{} is how each line is formatted. Raw concatenation
+of each line is performed, without any spacing or width adjustment.
+\q{}, if true, indicates this generator produces an
+infinite number of lines and termination should be determined without
+it.
+
+\subsubsection*{(wrap-lines ...)}
+
+Behaves like \q{cat}, except text is accumulated and lines are optimally
+wrapped to fit in the current width as in the Unix \p{fmt(1)} command.
+
+\subsubsection*{(justify ...)}
+
+Like \q{wrap-lines} except the lines are full-justified.
+
+\q{
+ (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
+
+\p{
+ (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.
+}
+
+\subsubsection*{(fmt-file )}
+
+Simply displayes the contents of the file \q{} a line at a
+time, so that in typical formatters such as \q{columnar} only constant
+memory is consumed, making this suitable for formatting files of
+arbitrary size.
+
+\subsubsection*{(line-numbers [])}
+
+A convenience utility, just formats an infinite stream of numbers (in
+the current radix) beginning with \q{}, which defaults to \q{1}.
+
+The Unix \q{nl(1)} utility could be implemented as:
+
+\q{
+ (fmt #t (columnar 6 'right 'infinite (line-numbers)
+ " " (fmt-file "read-line.scm")))
+}
+
+\p{
+ 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 #\newline))
+ 7 (list->string (reverse res))
+ 8 (lp (cons c res)))))))
+}
+
+\section{C Formatting}
+
+\subsection{C Formatting Basics}
+
+For purposes such as writing wrappers, code-generators, compilers or
+other language tools, people often need to generate or emit C code.
+Without a decent library framework it's difficult to maintain proper
+indentation. In addition, for the Scheme programmer it's tedious to
+work with all the context sensitivities of C, such as the expression
+vs. statement distinction, special rules for writing preprocessor
+macros, and when precedence rules require parenthesis. Fortunately,
+context is one thing this formatting library is good at keeping
+track of. The C formatting interface tries to make it as easy as
+possible to generate C code without getting in your way.
+
+There are two approaches to using the C formatting extensions -
+procedural and sexp-oriented (described in \ref{csexprs}). In the
+procedural interface, C operators are made available as formatters
+with a "c-" prefix, literals are converted to their C equivalents and
+symbols are output as-is (you're responsible for making sure they are
+valid C identifiers). Indentation is handled automatically.
+
+ \q{(fmt #t (c-if 1 2 3))}
+
+outputs
+
+\p{
+ if (1) {
+ 2;
+ } else {
+ 3;
+ }
+}
+
+In addition, the formatter knows when you're in an expression and
+when you're in a statement, and behaves accordingly, so that
+
+ \q{(fmt #t (c-if (c-if 1 2 3) 4 5))}
+
+outputs
+
+\p{
+ if (1 ? 2 : 3) {
+ 4;
+ } else {
+ 5;
+ }
+}
+
+Similary, \q{c-begin}, used for sequencing, will separate with
+semi-colons in a statement and commas in an expression.
+
+Moreover, we also keep track of the final expression in a function
+and insert returns for you:
+
+ \q{(fmt #t (c-fun 'int 'foo '() (c-if (c-if 1 2 3) 4 5)))}
+
+outputs
+
+\p{
+ int foo () {
+ if (1 ? 2 : 3) {
+ return 4;
+ } else {
+ return 5;
+ }
+ }
+}
+
+although it knows that void functions don't return.
+
+Switch statements insert breaks by default if they don't return:
+
+\q{
+ (fmt #t (c-switch 'y
+ (c-case 1 (c+= 'x 1))
+ (c-default (c+= 'x 2))))
+}
+
+\p{
+ switch (y) {
+ case 1:
+ x += 1;
+ break;
+ default:
+ x += 2;
+ break;
+ }
+}
+
+though you can explicitly fallthrough if you want:
+
+\q{
+ (fmt #t (c-switch 'y
+ (c-case/fallthrough 1 (c+= 'x 1))
+ (c-default (c+= 'x 2))))
+}
+
+\p{
+ switch (y) {
+ case 1:
+ x += 1;
+ default:
+ x += 2;
+ break;
+ }
+}
+
+Operators are available with just a "c" prefix, e.g. c+, c-, c*, c/,
+etc. \q{c++} is a prefix operator, \q{c++/post} is postfix. ||, | and
+|= are written as \q{c-or}, \q{c-bit-or} and \q{c-bit-or=} respectively.
+
+Function applications are written with \q{c-apply}. Other control
+structures such as \q{c-for} and \q{c-while} work as expected. The full
+list is in the procedure index below.
+
+When a C formatter encounters an object it doesn't know how to write
+(including lists and records), it outputs them according to the
+format state's current \q{'gen} variable. This allows you to specify
+generators for your own types, e.g. if you are using your own AST
+records in a compiler.
+
+If the \q{'gen} variable isn't set it defaults to the \q{c-expr/sexp}
+procedure, which formats an s-expression as if it were C code. Thus
+instead of \q{c-apply} you can just use a list. The full API is
+available via normal s-expressions - formatters that aren't keywords
+in C are prefixed with a % or otherwise made invalid C identifiers so
+that they can't be confused with function application.
+
+
+\subsection{C Preprocessor Formatting}
+
+C preprocessor formatters also properly handle their surrounding
+context, so you can safely intermix them in the normal flow of C
+code.
+
+\q{
+ (fmt #t (c-switch 'y
+ (c-case 1 (c= 'x 1))
+ (cpp-ifdef 'H_TWO (c-case 2 (c= 'x 4)))
+ (c-default (c= 'x 5))))
+}
+
+\p{
+ switch (y) {
+ case 1:
+ x = 1;
+ break;
+
+ #ifdef H_TWO
+ case 2:
+ x = 4;
+ break;
+ #endif /* H_TWO */
+ default:
+ x = 5;
+ break;
+ }
+}
+
+Macros can be handled with \q{cpp-define}, which knows to wrap
+individual variable references in parenthesis:
+
+ \q{(fmt #t (cpp-define '(min x y) (c-if (c< 'x 'y) 'x 'y)))}
+
+\p{
+ #define min(x, y) (((x) < (y)) ? (x) : (y))
+}
+
+As with all C formatters, the CPP output is pretty printed as
+needed, and if it wraps over several lines the lines are terminated
+with a backslash.
+
+To write a C header file that is included at most once, you can wrap
+the entire body in \q{cpp-wrap-header}:
+
+\q{
+ (fmt #t (cpp-wrap-header "FOO_H"
+ (c-extern (c-prototype 'int 'foo '()))))
+}
+
+\p{
+ #ifndef FOO_H
+ #define FOO_H
+
+ extern int foo ();
+
+ #endif /* ! FOO_H */
+}
+
+
+\subsection{Customizing C Style}
+
+The output uses a simplified K&R style with 4 spaces for indentation
+by default. The following state variables let you override the
+style:
+
+\subsubsection*{'indent-space}
+
+how many spaces to indent bodies, default \q{4}
+
+\subsubsection*{'switch-indent-space}
+
+how many spaces to indent switch clauses, also defaults to \q{4}
+
+\subsubsection*{'newline-before-brace?}
+
+insert a newline before an open brace (non-K&R), defaults to \q{#f}
+
+\subsubsection*{'braceless-bodies?}
+
+omit braces when we can prove they aren't needed
+
+\subsubsection*{'non-spaced-ops?}
+
+omit spaces between operators and operands for groups of variables and
+literals (e.g. "a+b+3" instead of "a + b + 3"}
+
+\subsubsection*{'no-wrap?}
+
+Don't wrap function calls and long operator groups over mulitple
+lines. Functions and control structures will still use multiple
+lines.
+
+The C formatters also respect the \q{'radix} and \q{'precision} settings.
+
+
+\subsection{C Formatter Index}
+
+\subsubsection*{(c-if [ [ ...]])}
+
+Print a chain of if/else conditions. Use a final condition of \q{'else}
+for a final else clause.
+
+\subsubsection*{(c-for ...)}
+\subsubsection*{(c-while ...)}
+
+Basic loop constructs.
+
+\subsubsection*{(c-fun ...)}
+\subsubsection*{(c-prototype )}
+
+Output a function or function prototype. The parameters should be a
+list 2-element lists of the form \q{( )},
+which are output with DSP. A parameter can be abbreviated as just the
+symbol name, or \q{#f} can be passed as the type, in which case the
+\q{'default-type} state variable is used. The parameters may be a
+dotted list, in which case ellipses for a C variadic are inserted -
+the actual name of the dotted value is ignored.
+
+Types are just typically just symbols, or lists of symbols such as
+\q{'(const char)}. A complete description is given below in section
+\ref{ctypes}.
+
+These can also accessed as %fun and %prototype at the head of a list.
+
+\subsubsection*{(c-var [])}
+
+Declares and optionally initializes a variable. Also accessed as %var
+at the head of a list.
+
+\subsubsection*{(c-begin ...)}
+
+Outputs each of the s, separated by semi-colons if in a
+statement or commas if in an expression.
+
+\subsubsection*{(c-switch ...)}
+\subsubsection*{(c-case ...)}
+\subsubsection*{(c-case/fallthrough ...)}
+\subsubsection*{(c-default ...)}
+
+Switch statements. In addition to using the clause formatters,
+clauses inside a switch may be handled with a Scheme CASE-like list,
+with the car a list of case values and the cdr the body.
+
+\subsubsection*{(c-label )}
+\subsubsection*{(c-goto )}
+\subsubsection*{(c-return [])}
+\subsubsection*{c-break}
+\subsubsection*{c-continue}
+
+Manual labels and jumps. Labels can also be accessed as a list
+beginning with a colon, e.g. \q{'(: label1)}.
+
+\subsubsection*{(c-const )}
+\subsubsection*{(c-static )}
+\subsubsection*{(c-volatile )}
+\subsubsection*{(c-restrict )}
+\subsubsection*{(c-register )}
+\subsubsection*{(c-auto )}
+\subsubsection*{(c-inline )}
+\subsubsection*{(c-extern )}
+
+Declaration modifiers. May be nested.
+
+\subsubsection*{(c-extern/C ...)}
+
+Wraps body in an extern "C" { ... } for use with C++.
+
+\subsubsection*{(c-cast )}
+
+Casts an expression to a type. Also %cast at the head of a list.
+
+\subsubsection*{(c-typedef ...)}
+
+Creates a new type definition with one or more names.
+
+\subsubsection*{(c-struct [] [])}
+\subsubsection*{(c-union [] [])}
+\subsubsection*{(c-class [] [])}
+\subsubsection*{(c-attribute ...)}
+
+Composite type constructors. Attributes may be accessed as
+%attribute at the head of a list.
+
+\q{
+ (fmt #f (c-struct 'employee
+ '((short age)
+ ((char *) name)
+ ((struct (year month day)) dob))
+ (c-attribute 'packed)))
+}
+
+\p{
+ struct employee {
+ short age;
+ char* name;
+ struct {
+ int year;
+ int month;
+ int day;
+ } dob;
+ } __attribute__ ((packed));
+}
+
+\subsubsection*{(c-enum [] )}
+
+Enumerated types. \q{} may be strings, symbols, or lists of
+string or symbol followed by the enum's value.
+
+\subsubsection*{(c-comment ...)}
+
+Outputs the \q{}s wrapped in C's /* ... */ comment. Properly
+escapes nested comments inside in an Emacs-friendly style.
+
+\subsection{C Preprocessor Formatter Index}
+
+\subsubsection*{(cpp-include )}
+
+If file is a string, outputs in it "quotes", otherwise (as a symbol
+or arbitrary formatter) it outputs it in brackets.
+
+ \q{(fmt #f (cpp-include 'stdio.h))}
+
+ \q{=> "#include \n"}
+
+ \q{(fmt #f (cpp-include "config.h"))}
+
+ \q{=> "#include \"config.h\"\n"}
+
+\subsubsection*{(cpp-define [])}
+
+Defines a preprocessor macro, which may be just a name or a list of
+name and parameters. Properly wraps the value in parenthesis and
+escapes newlines. A dotted parameter list will use the C99 variadic
+macro syntax, and will also substitute any references to the dotted
+name with \p{__VA_ARGS__}:
+
+ \q{(fmt #t (cpp-define '(eprintf . args) '(fprintf stderr args)))}
+
+\p{
+ #define eprintf(...) (fprintf(stderr, __VA_ARGS__))
+}
+
+\subsubsection*{(cpp-if [ ...])}
+\subsubsection*{(cpp-ifdef [ ...])}
+\subsubsection*{(cpp-ifndef [ ...])}
+\subsubsection*{(cpp-elif [ ...])}
+\subsubsection*{(cpp-else ...)}
+
+Conditional compilation.
+
+\subsubsection*{(cpp-line [])}
+
+Line number information.
+
+\subsubsection*{(cpp-pragma ...)}
+\subsubsection*{(cpp-error ...)}
+\subsubsection*{(cpp-warning ...)}
+
+Additional preprocessor directives.
+
+\subsubsection*{(cpp-stringify )}
+
+Stringifies \q{} by prefixing the # operator.
+
+\subsubsection*{(cpp-sym-cat ...)}
+
+Joins the \q{} into a single preprocessor token with the ##
+operator.
+
+\subsubsection*{(cpp-wrap-header ...)}
+
+Wrap an entire header to only be included once.
+
+\subsubsection*{Operators:}
+
+\q{
+c++ c-- c+ c- c* c/ c% c& c^ c~ c! c&& c<< c>> c== c!=
+c< c> c<= c>= c= c+= c-= c*= c/= c%= c&= c^= c<<= c>>=
+c++/post c--/post c-or c-bit-or c-bit-or=
+}
+
+\subsection{C Types}
+\label{ctypes}
+
+Typically a type is just a symbol such as \q{'char} or \q{'int}. You
+can wrap types with modifiers such as \q{c-const}, but as a
+convenience you can just use a list such as in \q{'(const unsignedchar *)}.
+You can also nest these lists, so the previous example is
+equivalent to \q{'(* (const (unsigned char)))}.
+
+Pointers may be written as \q{'(%pointer )} for readability -
+\q{%pointer} is exactly equivalent to \q{*} in types.
+
+Unamed structs, classes, unions and enums may be used directly as
+types, using their respective keywords at the head of a list.
+
+Two special types are the %array type and function pointer type. An
+array is written:
+
+ \q{(%array [