Cmon
provides a few tools for printing values. This "object notation" mimics the syntax of OCaml literal values: tuples, named constructors and records.
No parser is provided (yet?). The library is intended to print values, for logging and debugging, that can be copy-pasted to an OCaml toplevel.
The unusual feature is that the printer makes sharing explicit: let-binding are introduced at the optimal place by computing dominating nodes. This is particularly convenient for printing the internal structures of symbolic manipulation tools (e.g. typecheckers) that can internally make use of sharing a lot.
A few samples, from the toplevel:
# #require "cmon";;
# #install_printer Cmon.format;;
# Cmon.unit
- : Cmon.t = ()
# Cmon.record ["int", Cmon.int 42; "bool", Cmon.bool false];;
- : Cmon.t = { int = 42; bool = false; }
# let ty1 = Cmon.construct "Ty_arrow" [
Cmon.construct "Ty_int" [];
Cmon.construct "Ty_int" []
];;
val ty1 : Cmon.t = Ty_arrow (Ty_int, Ty_int)
# (* Printing the type derivation for a fictitious, simple,
programming language to illustrate sharing of sub values *)
Cmon.crecord "Let_binding" [
"name", Cmon.string "id_int";
"typ", ty1;
"body", Cmon.crecord "Exp_fun" [
"var", Cmon.string "x";
"typ", ty1;
"body", Cmon.construct "Exp_ident" [Cmon.string "x"]
]
];;
- : Cmon.t =
let v0 = Ty_arrow (Ty_int, Ty_int) in
Let_binding {
name = "id_int";
typ = v0;
body = Exp_fun { var = "x"; typ = v0; body = Exp_ident "x"; };
}
The library is distributed on opam:
$ opam install cmon
Add cmon
in the libraries
of a dune
file to use it.
Simple values are built using the functions unit
, bool
, char
, int
, float
, string
, tuple
, record
, nil
, cons
, list
, constant
, construct
and crecord
.
# #require "cmon";;
# #install_printer Cmon.format;;
# open Cmon;;
# unit;;
- : t = ()
# bool false;;
- : t = false
# int 10;;
- : t = 10
# char 'a';;
- : t = 'a'
# float 10.0;;
- : t = 10.
# string "foo";;
- : t = "foo"
# tuple [int 42; bool true];;
- : t = (42, true)
# record ["field1", int 1; "field2", char 'b'];;
- : t = { field1 = 1; field2 = 'b'; }
# nil;;
- : t = []
# cons (int 1) nil;;
- : t = [ 1 ]
# cons (int 1) (constant "x");;
- : t = 1 :: x
# cons (int 1) (constant "xs");;
- : t = 1 :: xs
# list [int 1; int 2; int 3];;
- : t = [ 1; 2; 3 ]
# crecord "Inline_record" ["field1", int 1; "field2", char 'b'];;
- : t = Inline_record { field1 = 1; field2 = 'b'; }
Sharing is enabled by default for all compound values and strings.
# let twice x = Cmon.tuple [x;x];;
# twice (string "foo-bar-baz");;
- : t = let v0 = "foo-bar-baz" in
(v0, v0)
# twice (construct "None" []);;
- : t = (None, None)
# twice (construct "Some" [bool true]);;
- : t = let v0 = Some true in
(v0, v0)
To opt out, use the unshared_*
variants of the main functions to opt out: unshared_construct
, unshared_crecord
, unshared_list
, unshared_record
, unshared_string
, unshared_tuple
.
# twice (unshared_string "foo-bar-baz");;
- : t = ("foo-bar-baz", "foo-bar-baz")
# twice (unshared_construct "Some" [bool true]);;
- : t = (Some true, Some true)
Sharing is based on the physical identities of Cmon.t
values, not their structure:
# let s = string "foo-bar-baz" in
Cmon.tuple [s; s];;
- : t = let v0 = "foo-bar-baz" in
(v0, v0)
# Cmon.tuple [string "foo-bar-baz"; string "foo-bar-baz"];;
- : t = ("foo-bar-baz", "foo-bar-baz")
Once composed, a Cmon.t
value can be converted to a PPrint.document
or directly sent to a Format.formatter
:
val Cmon.print : t -> PPrint.document
val Cmon.format : Format.formatter -> t -> unit
Cmon.t
is defined as an algebraic type:
type t =
| Unit (* () *)
| Nil (* [] *)
| Bool of bool (* true, false *)
| Char of char (* 'x' *)
| Int of int (* 0, 1, ... *)
| Int32 of int32 (* 0l, 1l, ... *)
| Int64 of int64 (* 0L, 1L, ... *)
| Nativeint of nativeint (* 0n, 1n, ... *)
| Float of float (* 0.0, 1.0, ... *)
| Constant of string (* constant constructor, e.g None *)
| Cons of {id: id; car: t; cdr: t} (* x :: xs *)
| String of {id: id; data: string} (* "Foo" *)
| Tuple of {id: id; data: t list} (* (a, b, c) ... *)
| Record of {id: id; data: (string * t) list} (* {a: va; b: vb} *)
| Constructor of {id: id; tag: string; data: t} (* Some foo *)
| Array of {id: id; data: t array}
| Lazy of {id: id; data: t lazy_t}
| Var of id (* x *)
| Let of {id: id; recursive: bool; bindings: (var * t) list; body: t}
It is possible to match on Cmon.t
values, inspect their structure, and create
new values. This is useful for debugging and understanding how Cmon works but
not recommended in normal use (one could easily create "unbound" variables by
messing with the structure). Stick to the public functions.
The Cmon.explicit_sharing
function can reveal the sharing that would be
displayed by the print
functions:
# let c = Cmon.tuple [s; s];;
val c : t =
Tuple
{Cmon.id = 6;
data =
[String {Cmon.id = 4; data = "foo-bar-baz"};
String {Cmon.id = 4; data = "foo-bar-baz"}]}
# Cmon.explicit_sharing c;;
- : t =
Let
{Cmon.id = 5; bindings = [(0, String {Cmon.id = 1; data = "foo-bar-baz"})];
body = Tuple {Cmon.id = 0; data = [Var 0; Var 0]}}
Printing is done either with the print
or format
functions:
val Cmon.print : t -> PPrint.document
val Cmon.format : Format.formatter -> t -> unit
To print without introducing more sharing, the _as_is
variants are provided:
val Cmon.print_as_is : t -> PPrint.document
val Cmon.format_as_is : Format.formatter -> t -> unit
Mixing explicit_sharing
and print_as_is
can be useful to print composite
documents while "controlling" sharing boundaries.