package pretty_expressive

  1. Overview
  2. Docs

The pretty printer and document combinators, parameterized by a cost factory.

Parameters

Signature

type doc

The doc type

type cost = C.t

The cost type

Examples in the rest of this section assume that the program begins with

open Pretty_expressive

let cf = Printer.default_cost_factory ~page_width:10 ()
module P = Printer.Make (val cf)
open P

Pretty printing functions

val pretty_print_info : ?init_c:int -> Signature.renderer -> doc -> cost Util.info

pretty_print_info renderer d prints the document d by repeatedly calling renderer and outputs the debugging information as an info record.The optional ~init_c can be used to indicate that the printing begins at a non-zero column position.

val pretty_format_info : ?init_c:int -> doc -> string * cost Util.info

pretty_format_info is similar to pretty_print_info, but it prints to a string instead of a renderer.

val pretty_print : ?init_c:int -> Signature.renderer -> doc -> unit

pretty_print d is similar to pretty_print_info without debugging information.

Examples:

# print_string "Languages: ";
  pretty_print
    print_string
    (align (text "Racket" ^^ nl ^^
            text "OCaml" ^^ nl ^^
            text "Pyret"));;
Languages: Racket
OCaml
Pyret
- : unit = ()

# print_string "Languages: ";
  pretty_print
    ~init_c:11
    print_string
    (align (text "Racket" ^^ nl ^^
            text "OCaml" ^^ nl ^^
            text "Pyret"));;
Languages: Racket
           OCaml
           Pyret
- : unit = ()
val pretty_format : ?init_c:int -> doc -> string

pretty_format is similar to pretty_format, without debugging information.

Examples:

# pretty_format (text "Hello World" ^^ nl ^^
                 text "Hi All!") |> print_endline;;
Hello World
Hi All!
- : unit = ()
val pretty_format_debug : ?init_c:int -> doc -> string

pretty_format_debug is similar to pretty_format_info, but the debugging information is included as a part of the output string. The format is customizable via debug_format.

Examples:

# pretty_format_debug (text "Hello World" ^^ nl ^^
                       text "Hi All!") |> print_endline;;
1234567890
Hello Worl│d
Hi All!   │

is_tainted: false
cost: (1 0 1 0)
- : unit = ()

Text document

val text : string -> doc

text s is a document for textual content s; s must not contain a newline.

Examples:

# pretty_print print_string (text "Portal");;
Portal
- : unit = ()

Newline documents

val newline : string option -> doc

newline s is a document for a newline. When s is None, it flattens to fail. When s is not None, it flattens to text s. See flatten for more details.

val nl : doc

nl is a document for a newline that flattens to a single space.

val break : doc

break is a document for a newline that flattens to an empty string.

val hard_nl : doc

hard_nl is a document for a newline that fails to flatten.

Concatenation document

val (^^) : doc -> doc -> doc

a ^^ b is a document for concatenation of documents a and b without alignment. In the paper, the symbol <> is used for the operator. We use ^^ in the OCaml implementation instead to avoid shadowing the built-in not equal operator. This operator also known as the unaligned concatenation, which is widely used in traditional pretty printers.

See also Printer.MakeCompat for a functor that provides this operator under the symbol <>.

Examples:

let left_doc = text "Splatoon" ^^ nl ^^ text "Nier";;
let right_doc = text "Automata" ^^ nl ^^ text "FEZ";;
# pretty_print print_string (left_doc ^^ right_doc);;
Splatoon
NierAutomata
FEZ
- : unit = ()

By "without alignment," we mean that the right document is not treated as as box with a rigid structure. This makes it easy to format code in C-like languages, whose array expression, function call, and curly braces should not be rigid.

Choice document

val (<|>) : doc -> doc -> doc

a <|> b is a document for a choice between document a and b.

# let print_doc w =
    let cf = Printer.default_cost_factory ~page_width:w () in
    let module P = Printer.Make (val cf) in
    let open P in
    pretty_print
      print_string
      (text "Chrono Trigger" <|>
       (text "Octopath" ^^ nl ^^ text "Traveler"));;
val print_doc : int -> unit = <fun>

# print_doc 10;;
Octopath
Traveler
- : unit = ()

# print_doc 15;;
Chrono Trigger
- : unit = ()

See also Best Practice for Document Construction

Indentation documents

val align : doc -> doc

align d is a document that aligns d at the column position.

Examples:

# pretty_print print_string (left_doc ^^ align right_doc);;
Splatoon
NierAutomata
    FEZ
- : unit = ()

The aligned concatenation operator (<+>) is a derived combinator that composes (^^) and align together. It is especially useful for languages that uses the the box model for code styling.

val nest : int -> doc -> doc

nest n d is a document that increments the indentation level by n when rendering d.

Examples:

# pretty_print
    print_string
    (text "when 1 = 2:" ^^ nest 4 (nl ^^ text "print 'oh no!'"));;
when 1 = 2:
    print 'oh no!'
- : unit = ()

The increment does not affect content on the current line. In the following example, when 1 = 2: is not further indented.

# pretty_print
    print_string
    (nest 4 (text "when 1 = 2:" ^^ nl ^^ text "print 'oh no!'"));;
when 1 = 2:
    print 'oh no!'
- : unit = ()
val reset : doc -> doc

reset d is a document that resets indentation level to 0 in d. This is especially useful for formatting multi-line strings and multi-line comments.

Examples:

# let s_d = reset (text "#<<EOF" ^^ nl ^^
                   text "Zelda" ^^ nl ^^
                   text "Baba is you" ^^ nl ^^
                   text "EOF");;
val s_d : doc = <abstr>

# pretty_print
    print_string
    (text "when 1 = 2:" ^^ nest 4 (nl ^^ text "print " ^^ s_d));;
when 1 = 2:
    print #<<EOF
Zelda
Baba is you
EOF
- : unit = ()

Cost document

val cost : cost -> doc -> doc

cost c d is a document that artificially adds cost c to d.

In the below example, we artificially adds overflow to text "CrossCode", making it a non-optimal choice, even though text "CrossCode" would have been the optimal choice had cost not been used.

Examples:

# pretty_format_debug (cost (1, 0, 0, 0) (text "CrossCode") <|>
                      (text "Final" ^^ nl ^^ text "Fantasy")) |> print_endline;;
1234567890
Final     │
Fantasy   │

is_tainted: false
cost: (0 0 1 0)
- : unit = ()

# pretty_format_debug (text "CrossCode" <|>
                      (text "Final" ^^ nl ^^ text "Fantasy")) |> print_endline;;
1234567890
CrossCode │

is_tainted: false
cost: (0 0 0 0)
- : unit = ()

cost is especially useful in combination with a custom cost factory. See the section for further details.

Filler documents

val two_columns : (doc * doc) list -> doc

two_columns ds is a document that lays out the documents in ds in two columns.

Note that this is not quite a table layout, because in each row, the right column will start at the same line as the last line of the left column.

Also note that some rows may overflow the column separator (e.g. in order to avoid the global overflow over the page width limit). The function two_columns_overflow can be used to customize this behavior.

The indentation level is set to the initial current column position (in the same manner as align) so that on entering a new line, the left column of the next row starts at the right position.

Unlike fill or fillBreak from Wadler/Leijen's pretty printer, which requires users to specify a fixed position of the column separator, two_columns will find the position automatically, and will find the leftmost one.

Examples:

Following example is taken from https://hackage.haskell.org/package/wl-pprint-1.2.1/docs/Text-PrettyPrint-Leijen.html

# let types = [ ("empty",     "Doc") ;
                ("nest",      "Int -> Doc -> Doc") ;
                ("linebreak", "Doc") ];;
val types : (string * string) list =
  [("empty", "Doc"); ("nest", "Int -> Doc -> Doc"); ("linebreak", "Doc")]

# let print_doc w =
    let cf = Printer.default_cost_factory ~page_width:w () in
    let module P = Printer.Make (val cf) in
    let open P in
    let d = text "let " ^^
            two_columns (List.map
                           (fun (n, t) ->
                              (text n, text " :: " ^^ text t))
                           types) in
    pretty_format_debug d |> print_endline;;
val print_doc : int -> unit = <fun>

# print_doc 34;;
1234567890123456789012345678901234
let empty     :: Doc              │
    nest      :: Int -> Doc -> Doc│
    linebreak :: Doc              │

is_tainted: false
cost: (0 0 2 2)
- : unit = ()

# print_doc 33;;
123456789012345678901234567890123
let empty :: Doc                 │
    nest  :: Int -> Doc -> Doc   │
    linebreak :: Doc             │

is_tainted: false
cost: (0 4 2 1)
- : unit = ()

# print_doc 28;;
1234567890123456789012345678
let empty :: Doc            │
    nest :: Int -> Doc -> Do│c
    linebreak :: Doc        │

is_tainted: false
cost: (1 6 2 0)
- : unit = ()

# let print_doc_nl w =
    let cf = Printer.default_cost_factory ~page_width:w () in
    let module P = Printer.Make (val cf) in
    let open P in
    let d = text "let " ^^
            two_columns (List.map
                           (fun (n, t) ->
                              (text n ^^ (nl <|> empty),
                               text " :: " ^^ text t))
                           types) in
    pretty_format_debug d |> print_endline;;
val print_doc_nl : int -> unit = <fun>

# print_doc_nl 34;;
1234567890123456789012345678901234
let empty     :: Doc              │
    nest      :: Int -> Doc -> Doc│
    linebreak :: Doc              │

is_tainted: false
cost: (0 0 2 3)
- : unit = ()

# print_doc_nl 33;;
123456789012345678901234567890123
let empty :: Doc                 │
    nest  :: Int -> Doc -> Doc   │
    linebreak                    │
          :: Doc                 │

is_tainted: false
cost: (0 0 3 2)
- : unit = ()

# print_doc_nl 28;;
1234567890123456789012345678
let empty                   │
     :: Doc                 │
    nest                    │
     :: Int -> Doc -> Doc   │
    linebreak               │
     :: Doc                 │

is_tainted: false
cost: (0 0 5 0)
- : unit = ()

Failure document

val fail : doc

A document that always fails. It interacts with (<|>): failing branches are pruned away.

Examples:

# pretty_print print_string (text "Sea of Stars" ^^ fail);;
Exception: Failure "fails to render".

# pretty_print print_string ((text "Sea of Stars" ^^ fail) <|> text "Hades");;
Hades
- : unit = ()

Other derived combinators

val flatten : doc -> doc

flatten d is a document that replaces newlines and indentation spaces with what's specified in newline when rendering d.

Examples:

# pretty_print
    print_string
    (flatten (text "Fire Emblem" ^^ nl ^^ text "Awakening"));;
Fire Emblem Awakening
- : unit = ()

# pretty_print
    print_string
    (flatten (text "Mario + Rabbids" ^^ break ^^ text "Kingdom Battle"));;
Mario + RabbidsKingdom Battle
- : unit = ()

# pretty_print
    print_string
    (flatten (text "XCOM 2" ^^ hard_nl ^^ text "War of the Chosen"));;
Exception: Failure "fails to render".

# pretty_print
    print_string
    (flatten (text "Tactics Ogre" ^^
              newline (Some ": ") ^^
              text "Reborn"));;
Tactics Ogre: Reborn
- : unit = ()
val group : doc -> doc

group d is a shorthand for d <|> flatten d. This combinator is a part of most traditional pretty printers.

val (<+>) : doc -> doc -> doc

a <+> b is a shorthand for a ^^ align b. It is also known as the aligned concatenation.

val (<$>) : doc -> doc -> doc

a <$> b is a shorthand for a ^^ hard_nl ^^ b.

val (<->) : doc -> doc -> doc

a <-> b is a shorthand for flatten a <+> b. This is especially useful when combined with hard_nl and (<|>): it can be used when we want to do aligned concatenation, but don't want the left part to have multiple lines.

val fold_doc : (doc -> doc -> doc) -> doc list -> doc

fold_doc (++) ds is a shorthand for d_1 ++ d_2 ++ ... ++ d_n where d_1 d_2 ... d_n are drawn from ds.

val vcat : doc list -> doc

vcat ds is a shorthand for d_1 <$> d_2 <$> ... <$> d_n where d_1 d_2 ... d_n are drawn from ds.

val hcat : doc list -> doc

vcat ds is a shorthand for d_1 <-> d_2 <-> ... <-> d_n where d_1 d_2 ... d_n are drawn from ds.

val empty : doc

Equivalent to text ""

val space : doc

Equivalent to text " "

val comma : doc

Equivalent to text ","

val lbrack : doc

Equivalent to text "["

val rbrack : doc

Equivalent to text "]"

val lbrace : doc

Equivalent to text "{"

val rbrace : doc

Equivalent to text "}"

val lparen : doc

Equivalent to text "("

val rparen : doc

Equivalent to text ")"

val dquote : doc

Equivalent to text "\""

OCaml

Innovation. Community. Security.