Version française
Home     About     Download     Resources     Contact us    

Using the Format module

The pretty-printing facility provided by the Format module of Caml Light and Objective Caml's standard libraries is used to get a fancy display for printing routines. This module provides a “pretty-printing engine” that is intended to break lines in a nice way (let's say “automatically when it is necessary”).

Principles

Breaking of lines is based on 2 concepts:

Boxes

There are 4 types of boxes. (The most often used is the “hov” box type, so skip the rest at first reading).

Let me give an example. Suppose we can write 10 chars before the right margin (that indicates no more room). We represent any char as -, [ and ] indicates openning and closing of box and b stands for a break hint found in the ouput by the pretty-printing engine.

The output "--b--b--" is displayed like this (the b symbol stands for the value of the break that is explained below):

Printing spaces

Break hints are also used to output spaces (if the line is not split when the break is encountered, otherwise the new line indicates properly the separation between printing items). You output a break hint using print_break sp indent, and this sp integer is used to print “sp” spaces. Thus print_break sp ... may be thought as: print sp spaces or output a new line.

For instance, if b is break 1 0 in the output "--b--b--", we get

Generally speaking, a printing routine using "format", should not directly output white spaces: the routine should use break hints instead. (For instance print_space () that is a convenient abbrev for print_break 1 0 and outputs a single space or break the line.)

Indentation of new lines

The user gets 2 ways to fix the indentation of new lines:

Refinement on “hov” boxes

Packing and structural “hov” boxes

The “hov” box type is refined into two categories.

Differences between a packing and a structural “hov” box

The difference between a packing and a structural “hov” box is shown by a routine that closes boxes and parens at the end of printing: with packing boxes, the closure of boxes and parens do not lead to new lines if there is enough room on the line, whereas with structural boxes each break hint will lead to a new line. For instance, when printing "[(---[(----[(---b)]b)]b)]", where "b" is a break hint without extra indentation (print_cut ()). If "[" means opening of a packing “hov” box (open_hovbox), "[(---[(----[(---b)]b)]b)]" is printed as follows:

(---
 (----
  (---)))

If we replace the packing boxes by structural boxes (open_box), each break hint that precedes a closing paren can show the boxes structure, if it leads to a new line; hence "[(---[(----[(---b)]b)]b)]" is printed like this:

(---
 (----
  (---
  )
 )
)

Practical advices

When writing a pretty-printing routine, follow these simple rules:

  1. Boxes must be opened and closed consistently (open_* and close_box must be nested like parens).
  2. Never hesitate to open a box.
  3. Output many break hints, otherwise the pretty-printer is in a bad situation where it tries to do its best, which is always “worse than your bad”.
  4. Don't try to force spacing using explicit spaces in the character string. For each space you want in the output emit a break hint (print_space ()) unless you explicitly don't want the line to be broken here. For instance, imagine you want to pretty print a definition in a Caml like language, for instance let rec ident = expression. You will probably treat the first three spaces as “unbreakable spaces” and write them directly in the string constants for keywords, "let rec " before the identifier and " =" after it. However, the space preceding the expression will certainly be a break hint, since breaking the line after the =Don't try to force line breaking, let the pretty-printer do it for you: that's its only job.
  5. Never put newline characters directly in the strins to be printed: pretty printing engine will consider this newline character as any other character written on the current line and this will completely mess uopt the output. Instead of new line characters use line breaking hints: if those breaking hints must always result in new lines, it just means that the surrounding box must be a vertical box!
  6. End your main program by a print_newline () call, that flushes the pretty-printer tables (hence the ouput). (Note that the toplevel loop of the interactive system does it as well, just before a new input.)

Using the printf function

The format module provides a general printing facility “à la” printf. In addition to the usual convertion facility provided by printf, you can write pretty-printing indications directly into the string format (opening and closing boxes, indicating breaking hints, etc).

Pretty-printing annotations are introduced by the @ symbol, directly into the string format. Almost any function of the format module can be called from within a printf format. For instance

For instance

printf "@[<1>%s@ =@ %d@ %s@]@." "Prix TTC" 100 "Euros";;
Prix TTC = 100 Euros
- : unit = ()

A concrete example

Let me give a full example: the shortest non trivial example you could imagine, that is the $\lambda-$calculus :)

Thus the problem is to pretty-print the values of a concrete data type that modelize a language of expressions that defines functions and their applications to arguments.

First, I give the abstract syntax of lambda-terms, then a lexical analyzer and a parser for this language:

type lambda =
   | Lambda of string * lambda
   | Var of string
   | Apply of lambda * lambda;;

Now, I use the format library to print the lambda-terms: I follow the recursive shape of the preceding parser to write the pretty-printer, inserting here and there the desired break hints and opening (and closing) boxes:

open Format;;

let ident = print_string;;
let kwd = print_string;;
val ident : string -> unit = <fun>
val kwd : string -> unit = <fun>

let rec print_exp0 = function
  | Var s ->  ident s
  | lam -> open_hovbox 1; kwd "("; print_lambda lam; kwd ")"; close_box ()

and print_app = function
  | e -> open_hovbox 2; print_other_applications e; close_box ()

and print_other_applications f =
  match f with
  | Apply (f, arg) -> print_app f; print_space (); print_exp0 arg
  | f -> print_exp0 f

and print_lambda = function
  | Lambda (s, lam) ->
      open_hovbox 1;
      kwd "\\"; ident s; kwd "."; print_space(); print_lambda lam;
      close_box()
  | e -> print_app e;;
val print_app : lambda -> unit = <fun>
val print_other_applications : lambda -> unit = <fun>
val print_lambda : lambda -> unit = <fun>

In Caml Light, replace the first line by:

#open "format";;

Using printf

This can be equivalently written using printf:

let rec print_exp0 = function
  | Var s ->  ident s
  | lam -> printf "@[<1>(%a)@]" print_lambda lam

and print_app = function
  | e ->  printf "@[<2>%a@]" print_other_applications e

and print_other_applications f =
  match f with
  | Apply (f, arg) -> printf "%a@ %a" print_app f print_exp0 arg
  | f -> print_exp0 f

and print_lambda = function
 | Lambda (s, lam) ->
     printf "@[<1>%a%a%a@ %a@]" kwd "\\" ident s kwd "." print_lambda lam
 | e -> print_app e;;
val print_app : lambda -> unit = <fun>
val print_other_applications : lambda -> unit = <fun>
val print_lambda : lambda -> unit = <fun>

Using fprintf

We use the fprintf function and the pretty-printing functions get an extra argument, namely a pretty-printing formatter (the ppf argument) where printing will occur. This way the printing routines are a bit more general, since they may print on any formatter defined in the program, and furthermore they may be used in conjonction with the special %a format, that prints a printf argument with a supplied user's defined function (these user's functions must have a formatter as first argument).

Using fprintf, the lambda-terms printing routines can be written as follows:

let rec pr_exp0 ppf = function
  | Var s ->  ident ppf s
  | lam -> fprintf ppf "@[<1>(%a)@]" pr_lambda lam

and pr_app ppf = function
  | e ->  fprintf ppf "@[<2>%a@]" pr_other_applications e

and pr_other_applications ppf f =
  match f with
  | Apply (f, arg) -> fprintf ppf "%a@ %a" pr_app f pr_exp0 arg
  | f -> pr_exp0 ppf f

and pr_lambda ppf = function
 | Lambda (s, lam) ->
     fprintf ppf "@[<1>%a%a%a@ %a@]" kwd "\\" ident s kwd "." pr_lambda lam
 | e -> pr_app ppf e;;

let print_lambda = pr_lambda std_formatter;;