wl-pprint
TOC »
Description
wl-pprint is a Wadler/Leijen-style pretty-printing combinator library. It contains an algebraic type document, combinators for building documents, and a renderer that lays them out to fit a given line width, backtracking over group choice points as needed.
It is a port of Francois Pottier and Nicolas Pouillard's PPrint library (INRIA Paris-Rocquencourt), itself an adaptation of Daan Leijen's PPrint, based on the ideas in Philip Wadler's "A Prettier Printer". See:
- http://www.cs.uu.nl/~daan/pprint.html
- http://homepages.inf.ed.ac.uk/wadler/papers/prettier/prettier.pdf
- https://github.com/iraikov/pprint
wl-print works by buildling a document via text, ^^, group, and friends, described below. Output is produced by pretty or compact.
The central abstraction is the group / conditional-break pair: a group first tries to render its contents "flat" (as if on one line), and only falls back to breaking at the specified breaks, which are converted into actual newlines plus indentation if the flat rendering would not fit in the given width.
Author
Ivan Raikov
Repository
https://github.com/iraikov/chicken-wl-pprint
Requirements
API
Document construction
- document? xprocedure
Returns #t if x is a document, #f otherwise.
- emptyprocedure
empty represents the empty document, the identity element for ^^. It renders as nothing and is eliminated by concatenation: (^^ empty x) and (^^ x empty) are both just x.
- dcat doc ...procedure
Variadic convenience wrapper over ^^ for concatenating any number of documents.
- ifflat d1 d2procedure
Renders as d1 if the enclosing group is being rendered flat, as d2 otherwise. break, break0, and break1 (below) are all defined in terms of ifflat.
- hardlineprocedure
hardline represents a document that always renders as a newline, regardless of whether it occurs inside a flattened group. A hardline anywhere inside a group forces that group to break.
- char cprocedure
A document consisting of the single character c. Signals an error if c is #\newline; use hardline or break for line breaks instead.
- text-span s ofs lenprocedure
A document consisting of len characters of s starting at offset ofs. Renders to empty if len is 0.
- text sprocedure
A document consisting of the entire string s. s must not contain embedded newlines; use text-lines for that.
Indentation and alignment
- nest i xprocedure
Increases the indentation level used after any newline rendered inside x by i columns. i must be non-negative; signals an error otherwise.
- column fprocedure
Renders as (f k), where k is the column the renderer is currently at.
- nesting fprocedure
Renders as (f i), where i is the current indentation level.
- group xprocedure
Marks x as a unit that the renderer should try to render flat (all its breaks collapsed) if it fits within the width and ribbon; otherwise it is rendered with all its breaks turned into newlines. This is the central layout primitive of the library.
- align dprocedure
Renders d with its indentation level set to the current column, so continuation lines line up under wherever d started rather than under a fixed offset. Useful when the start column varies, e.g. after a variable-length prefix.
- indent i dprocedure
Like hang, but also prefixes d with i literal spaces: (hang i (^^ (blank i) d)).
Punctuation constants
Single-character documents for common punctuation, all defined via char:
lparen rparen langle rangle lbrace rbrace lbracket rbracket squote dquote bquote semi colon comma space dot sharp backslash equals qmark tilde at percent dollar caret ampersand star plus minus underscore bang bar
Breaks and text helpers
- break iprocedure
A conditional break: renders as i literal spaces when its enclosing group is flat, or as a newline (plus the current indentation) when it breaks.
- break0parameter
Shortcut for (ifflat empty hardline): a conditional break with no space when flat.
- break1parameter
Shortcut for (ifflat space hardline): a conditional break with one space when flat.
- text-lines sprocedure
Splits s on embedded newlines and joins the pieces with break1, so a multi-line string participates correctly in the enclosing group's flatten/break decision instead of dumping raw newlines into the middle of a line.
- words sprocedure
Splits s on runs of whitespace and lays the words out with grouped breaks between them, so a paragraph of text wraps at the current width like a text-fill.
Enclosing
- enclose l r xprocedure
Wraps x between documents l and r: (^^ l (^^ x r)).
- parens xprocedure
- braces xprocedure
- brackets xprocedure
- angles xprocedure
- squotes xprocedure
- dquotes xprocedure
- bquotes xprocedure
enclose instantiated with ( ), { }, [ ], < >, ' ', " ", and <code>` `</code> respectively.
Folds
- fold f docsprocedure
Right fold of two-argument document-combining procedure f over list docs, with empty as the base case for '().
- fold1 f docsprocedure
Like fold, but a singleton list returns its one element unmodified rather than combining it with empty; '() still returns empty.
- fold1map f g docsprocedure
Like fold1, but maps g over each element of docs before combining with f.
- sepmap sep g docsprocedure
Maps g over docs and joins the results with separator document sep: (fold1map (lambda (x y) (^^ x (^^ sep y))) g docs).
Surround and seq
- surround n sep open-doc contents close-docprocedure
Wraps contents between open-doc and close-doc as a single group, with sep as the gap on each side and n columns of extra indentation when the group breaks. Open, gap, contents, and close all flatten or break together.
- surround1 open-txt contents close-txtprocedure
surround with n = 1, sep = break0, and string delimiters open-txt/close-txt wrapped in text.
- surround2 open-txt contents close-txtprocedure
surround with n = 2, sep = break1, and string delimiters open-txt/close-txt wrapped in text.
- soft-surround n sep open-doc contents close-docprocedure
Like surround, but the open/close gap and contents are each their own group, so they can flatten or break independently -- e.g. delimiters that stay on one line each while the body they enclose still wraps across several.
- seq indent-n brk empty-seq open-seq sep-seq close-seq xsprocedure
The general list-to-delimited-sequence builder: renders empty-seq for '(), otherwise (surround indent-n brk open-seq ... close-seq) around xs joined by sep-seq.
- seq1 open-txt sep-txt close-txtprocedure
Returns a procedure that renders a list of documents as a tuple-like sequence: surround1-style delimiters, break0 gap, elements separated by sep-txt followed by break1.
- seq2 open-txt sep-txt close-txtprocedure
Like seq1 but surround2-style: 2 columns of indentation, break1 gap.
Rendering
- pretty rfrac width port docprocedure
Pretty-prints doc to port. width is the maximum number of characters per line; rfrac is the ribbon width as a fraction of width (the maximum number of non-indentation characters per line). 1.0 means the ribbon is the same as the width.
- compact port docprocedure
Prints doc to port with no indentation and no width tracking: every group renders flat and only hardlines produce actual newlines. Useful for a fast single-line (or hardline-delimited) rendering, e.g. for embedding a document in a comment.
Value-representation helpers
Helpers for printing debug representations of algebraic values, e.g. tuples, variants, records, options, lists, and base types -- in an ML-like notation. Useful as a quick write-style pretty-printer for ad hoc data while debugging.
- ml-tuple docsprocedure
Renders a list of documents as an ML tuple: (seq1 "(" "," ")").
- ml-variant type-name cons-name tag argsprocedure
Renders a constructor application as ConsName (if args is '()) or ConsName(arg, ...). type-name and tag are accepted for parity with the SML source but not used in the rendering.
- ml-record type-name fieldsprocedure
Renders an association list of (name . doc) pairs as an ML-style record: { name1=doc1, name2=doc2 }.
- ml-option f xprocedure
Renders (f x) wrapped as Some(...) if x is truthy, or None if x is #f. Scheme has no direct analog of SML's option type, so #f doubles as None; this cannot distinguish None from Some #f, which is acceptable for the debug-printing use this helper is intended for.
- ml-list f xsprocedure
Renders (map f xs) as an ML-style list: [ e1, e2, ... ].
- ml-string sprocedure
Renders string s via text-lines (so embedded newlines still participate correctly in group flattening).
- ml-int iprocedure
- ml-real xprocedure
- ml-bool bprocedure
Render an integer, a real number, or a boolean (true/false).
- ml-char cprocedure
Renders character c, escaping it in the manner of (though not byte-identical to) SML's Char.toString: printable ASCII passes through, common control characters get a backslash escape (\n, \t, \r, \\), anything else falls back to a decimal code escape.
- ml-unknown type-name xprocedure
Renders as <type-name>, a placeholder for values with no dedicated ml-* representation. x is accepted for parity with the SML source but not used in the rendering.
Examples
(import scheme (chicken port) wl-pprint) ;; A call-like document that breaks onto multiple lines only if it ;; doesn't fit in the given width. (define call-doc (group (nest 2 (dcat (text "foo(") break0 (text "a,") break1 (text "b") break0 (text ")"))))) (display (call-with-output-string (lambda (p) (pretty 1.0 80 p call-doc)))) ;; => foo(a, b) (display (call-with-output-string (lambda (p) (pretty 1.0 5 p call-doc)))) ;; => foo( ;; a, ;; b ;; ) ;; ml-* helpers, for quick debug-printing of algebraic values. (define point (ml-record "point" (list (cons "x" (ml-int 1)) (cons "y" (ml-int 2))))) (display (call-with-output-string (lambda (p) (pretty 1.0 80 p point)))) ;; => { x=1, y=2 }
See also examples/tutorial.scm in the repository for a longer, runnable tour of the whole API, including a worked example that formats a tiny C-like declaration syntax.
License
<pre> The MIT License (MIT)
Copyright (c) 2026 Ivan Raikov
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. </pre>
Version history
- 1.0
- Initial release.