chickadee » wl-pprint

wl-pprint

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:

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.

^^ x yprocedure

Concatenates two documents, eliminating empty on either side.

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.

blank nprocedure

A document consisting of n literal space characters. Renders to empty if n is 0.

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.

hang i dprocedure

align plus i extra columns of indentation: (align (nest i d)).

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).

group1 dprocedure
group2 dprocedure

(group (nest 1 d)) and (group (nest 2 d)).

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.

Contents »