// Formatters and their public API, a port of OCaml's `format.ml`.
//
// Copyright 1996 Institut National de Recherche en Informatique et en
// Automatique (OCaml), distributed under the terms of the GNU Lesser
// General Public License version 2.1, with the special exception on
// linking described in the file LICENSE.
///|
/// Length of a string in UTF-8 bytes, which is its width for the
/// pretty-printer, like OCaml's `String.length` on UTF-8 strings.
pub fn utf8_length(s : StringView) -> Int {
let mut n = 0
for c in s {
let code = c.to_int()
n += if code < 0x80 {
1
} else if code < 0x800 {
2
} else if code < 0x10000 {
3
} else {
4
}
}
n
}
///|
fn default_mark_open_stag(tag : Stag) -> String {
match tag {
StringTag(s) => "<" + s + ">"
OtherTag(_) => ""
}
}
///|
fn default_mark_close_stag(tag : Stag) -> String {
match tag {
StringTag(s) => "" + s + ">"
OtherTag(_) => ""
}
}
///|
/// The default tag functions: string tags are marked as `` and
/// ``, nothing is printed.
pub let default_stag_functions : StagFunctions = {
mark_open_stag: default_mark_open_stag,
mark_close_stag: default_mark_close_stag,
print_open_stag: _ => (),
print_close_stag: _ => (),
}
///|
/// Create a formatter from its output functions, like OCaml's
/// `formatter_of_out_functions`.
pub fn Formatter::of_out_functions(out : OutFunctions) -> Formatter {
// The initial state of the formatter contains a dummy box.
let queue = @deque.Deque([])
let sys_tok = { size: size_unknown, token: Begin(0, HOVBox), length: 0, }
queue.push_back(sys_tok)
let scan_stack = []
initialize_scan_stack(scan_stack)
scan_stack.push({ left_total: 1, queue_elem: sys_tok, })
let margin = 78L
let min_space_left = 10L
{
scan_stack,
format_stack: [],
tbox_stack: [],
tag_stack: [],
mark_stack: [],
margin,
min_space_left,
max_indent: margin - min_space_left,
space_left: margin,
current_indent: 0,
is_new_line: true,
left_total: 1,
right_total: 1,
curr_depth: 1,
max_boxes: 2147483647,
ellipsis: ".",
out,
print_tags: false,
mark_tags: false,
stag_functions: default_stag_functions,
queue,
}
}
///|
let blank_line : String = String::make(80, ' ')
///|
/// Output `n` spaces with an output function, 80 at a time, like OCaml's
/// `display_blanks`.
fn display_blanks(out_string : (StringView) -> Unit, n : Int) -> Unit {
let mut n = n
while n > 0 {
if n <= 80 {
out_string(blank_line.view(end_offset=n))
n = 0
} else {
out_string(blank_line)
n -= 80
}
}
}
///|
/// Create a formatter that outputs with `output` and flushes with `flush`;
/// newlines and spaces are output with `output` too. Like OCaml's
/// `make_formatter`.
pub fn Formatter::Formatter(
output : (StringView) -> Unit,
flush? : () -> Unit = () => (),
) -> Formatter {
let ppf = Formatter::of_out_functions({
out_string: output,
out_flush: flush,
out_newline: () => (),
out_spaces: _ => (),
out_indent: _ => (),
})
// newlines and spaces are output with the current output function of the
// formatter (see `set_output_functions`), like in OCaml
ppf.out = {
..ppf.out,
out_newline: () => (ppf.out.out_string)("\n"),
out_spaces: n => display_blanks(ppf.out.out_string, n),
out_indent: n => display_blanks(ppf.out.out_string, n),
}
ppf
}
///|
/// Create a formatter writing to a string builder, like OCaml's
/// `formatter_of_buffer`.
pub fn Formatter::of_buffer(buf : StringBuilder) -> Formatter {
Formatter(s => buf.write_view(s))
}
// Formatting functions
///|
/// Print a string, pretending that its width is `size`.
pub fn Formatter::print_as(self : Formatter, size : Int, s : String) -> Unit {
if self.curr_depth < self.max_boxes {
self.enqueue_string_as(size.to_int64(), s)
}
}
///|
/// Print a string in the current box. Its width is its length in UTF-8
/// bytes.
pub fn Formatter::print_string(self : Formatter, s : String) -> Unit {
self.print_as(utf8_length(s), s)
}
///|
/// Print an integer.
pub fn Formatter::print_int(self : Formatter, i : Int) -> Unit {
self.print_string(i.to_string())
}
///|
/// Print a floating-point number in OCaml's syntax, like OCaml's
/// `string_of_float` (12 significant digits).
pub fn Formatter::print_float(self : Formatter, f : Double) -> Unit {
self.print_string(string_of_float(f))
}
///|
/// Print a boolean.
pub fn Formatter::print_bool(self : Formatter, b : Bool) -> Unit {
self.print_string(if b { "true" } else { "false" })
}
///|
/// Print a character. Its width is 1.
pub fn Formatter::print_char(self : Formatter, c : Char) -> Unit {
self.print_as(1, c.to_string())
}
///|
/// Print nothing.
pub fn Formatter::print_nothing(_self : Formatter) -> Unit {
}
// Boxes
///|
/// Open a horizontal box: break hints never split the line.
pub fn Formatter::open_hbox(self : Formatter) -> Unit {
self.open_box_gen(0, HBox)
}
///|
/// Open a vertical box: every break hint splits the line; `indent` is
/// added to the current indentation.
pub fn Formatter::open_vbox(self : Formatter, indent : Int) -> Unit {
self.open_box_gen(indent, VBox)
}
///|
/// Open a horizontal/vertical box: horizontal if it fits on the line,
/// otherwise vertical.
pub fn Formatter::open_hvbox(self : Formatter, indent : Int) -> Unit {
self.open_box_gen(indent, HVBox)
}
///|
/// Open a horizontal-or-vertical compacting box: break hints split the line
/// only when the material doesn't fit on it.
pub fn Formatter::open_hovbox(self : Formatter, indent : Int) -> Unit {
self.open_box_gen(indent, HOVBox)
}
///|
/// Open a structural compacting box: like `open_hovbox`, but break hints
/// also split the line if that reduces the indentation.
pub fn Formatter::open_box(self : Formatter, indent : Int) -> Unit {
self.open_box_gen(indent, Box)
}
// Break hints
///|
/// Generalized break hint: `fits` = (before, width, after) is printed if
/// the line is not split, `breaks` = (before, offset, after) if it is.
pub fn Formatter::print_custom_break(
self : Formatter,
fits~ : (String, Int, String),
breaks~ : (String, Int, String),
) -> Unit {
let (before, width, after) = fits
if self.curr_depth < self.max_boxes {
let size = -self.right_total
let token = Break(fits~, breaks~)
let length = (utf8_length(before) + width + utf8_length(after)).to_int64()
self.scan_push(true, { size, token, length, })
}
}
///|
/// Break hint: print `width` spaces if the line is not split, otherwise
/// split the line and add `offset` to the indentation.
pub fn Formatter::print_break(
self : Formatter,
width : Int,
offset : Int,
) -> Unit {
self.print_custom_break(fits=("", width, ""), breaks=("", offset, ""))
}
///|
/// Break hint printing a space if the line is not split (`@ `).
pub fn Formatter::print_space(self : Formatter) -> Unit {
self.print_break(1, 0)
}
///|
/// Break hint printing nothing if the line is not split (`@,`).
pub fn Formatter::print_cut(self : Formatter) -> Unit {
self.print_break(0, 0)
}
///|
/// Force a new line in the current box. Not the normal way of
/// pretty-printing: prefer break hints within vertical boxes.
pub fn Formatter::force_newline(self : Formatter) -> Unit {
if self.curr_depth < self.max_boxes {
self.enqueue_advance({ size: 0, token: Newline, length: 0, })
}
}
///|
/// Execute the next formatting command only if the preceding line has just
/// been split; otherwise, ignore it.
pub fn Formatter::print_if_newline(self : Formatter) -> Unit {
if self.curr_depth < self.max_boxes {
self.enqueue_advance({ size: 0, token: IfNewline, length: 0, })
}
}
///|
/// Close all the opened boxes and print all the pending text, then flush
/// the output device.
pub fn Formatter::print_flush(self : Formatter) -> Unit {
self.flush_queue(false)
(self.out.out_flush)()
}
///|
/// Like `print_flush`, followed by a newline.
pub fn Formatter::print_newline(self : Formatter) -> Unit {
self.flush_queue(true)
(self.out.out_flush)()
}
// Tabulation boxes
///|
/// Open a tabulation box.
pub fn Formatter::open_tbox(self : Formatter) -> Unit {
self.curr_depth = self.curr_depth + 1
if self.curr_depth < self.max_boxes {
self.enqueue_advance({
size: 0,
token: TBegin({ tabs: @list.empty(), }),
length: 0,
})
}
}
///|
/// Close the most recently opened tabulation box.
pub fn Formatter::close_tbox(self : Formatter) -> Unit {
if self.curr_depth > 1 {
if self.curr_depth < self.max_boxes {
self.enqueue_advance({ size: 0, token: TEnd, length: 0, })
self.curr_depth = self.curr_depth - 1
}
}
}
///|
/// Break hint in a tabulation box: move to the next tabulation stop, then
/// print `width` spaces; if there is no room, split the line and add
/// `offset` to the indentation.
pub fn Formatter::print_tbreak(
self : Formatter,
width : Int,
offset : Int,
) -> Unit {
if self.curr_depth < self.max_boxes {
let size = -self.right_total
self.scan_push(true, {
size,
token: TBreak(width, offset),
length: width.to_int64(),
})
}
}
///|
/// Move to the next tabulation stop (`print_tbreak(0, 0)`).
pub fn Formatter::print_tab(self : Formatter) -> Unit {
self.print_tbreak(0, 0)
}
///|
/// Set a tabulation stop at the current insertion point.
pub fn Formatter::set_tab(self : Formatter) -> Unit {
if self.curr_depth < self.max_boxes {
self.enqueue_advance({ size: 0, token: STab, length: 0, })
}
}
// Maximum number of boxes
///|
/// Set the maximum number of simultaneously opened boxes (must be > 1);
/// material in deeper boxes is printed as the ellipsis.
pub fn Formatter::set_max_boxes(self : Formatter, n : Int) -> Unit {
if n > 1 {
self.max_boxes = n
}
}
///|
/// The maximum number of simultaneously opened boxes.
pub fn Formatter::get_max_boxes(self : Formatter) -> Int {
self.max_boxes
}
///|
/// Whether the maximum number of opened boxes is reached.
pub fn Formatter::over_max_boxes(self : Formatter) -> Bool {
self.curr_depth == self.max_boxes
}
///|
/// Set the text printed for boxes beyond the maximum (`.` by default).
pub fn Formatter::set_ellipsis_text(self : Formatter, s : String) -> Unit {
self.ellipsis = s
}
///|
/// The text printed for boxes beyond the maximum.
pub fn Formatter::get_ellipsis_text(self : Formatter) -> String {
self.ellipsis
}
// Geometry
///|
fn pp_limit(n : Int64) -> Int64 {
if n < pp_infinity {
n
} else {
pp_infinity - 1
}
}
///|
fn Formatter::set_min_space_left(self : Formatter, n : Int64) -> Unit {
if n >= 1 {
let n = pp_limit(n)
self.min_space_left = n
self.max_indent = self.margin - self.min_space_left
self.rinit()
}
}
///|
/// Set the maximum indentation: boxes opened beyond it are rejected to the
/// left. Ignored if `n <= 1`.
pub fn Formatter::set_max_indent(self : Formatter, n : Int) -> Unit {
self.set_max_indent64(n.to_int64())
}
///|
fn Formatter::set_max_indent64(self : Formatter, n : Int64) -> Unit {
if n > 1 {
self.set_min_space_left(self.margin - n)
}
}
///|
/// The maximum indentation.
pub fn Formatter::get_max_indent(self : Formatter) -> Int {
self.max_indent.to_int()
}
///|
/// Set the right margin (78 by default). Ignored if `n < 1`.
pub fn Formatter::set_margin(self : Formatter, n : Int) -> Unit {
if n >= 1 {
let n = pp_limit(n.to_int64())
self.margin = n
let new_max_indent = if self.max_indent <= self.margin {
// try to maintain max_indent to its actual value
self.max_indent
} else {
// if possible maintain min_space_left to its actual value; if this
// leads to a too small max_indent, take half of the new margin, if
// it is greater than 1
let a = self.margin - self.min_space_left
let b = self.margin / 2
let m = if a > b { a } else { b }
if m > 1 {
m
} else {
1
}
}
// rebuild invariants
self.set_max_indent64(new_max_indent)
}
}
///|
/// The right margin.
pub fn Formatter::get_margin(self : Formatter) -> Int {
self.margin.to_int()
}
///|
/// The geometry of a formatter.
pub(all) struct Geometry {
max_indent : Int
margin : Int
} derive(Eq, Debug)
///|
pub extend Geometry with Eq::{equal, not_equal}
///|
pub extend Geometry with Debug::{to_repr}
///|
/// Error raised by functions given invalid arguments.
pub(all) suberror InvalidArgument {
InvalidArgument(String)
} derive(Eq, Debug)
///|
pub extend InvalidArgument with Eq::{equal, not_equal}
///|
pub extend InvalidArgument with Debug::{to_repr}
///|
fn validate_geometry(g : Geometry) -> String? {
if g.max_indent < 2 {
Some("max_indent < 2")
} else if g.margin <= g.max_indent {
Some("margin <= max_indent")
} else {
None
}
}
///|
/// Whether a geometry is valid: `max_indent >= 2` and
/// `margin > max_indent`.
pub fn check_geometry(g : Geometry) -> Bool {
validate_geometry(g) is None
}
///|
fn Formatter::set_full_geometry(self : Formatter, g : Geometry) -> Unit {
self.set_margin(g.margin)
self.set_max_indent(g.max_indent)
}
///|
/// Set the margin and the maximum indentation, which must form a valid
/// geometry.
pub fn Formatter::set_geometry(
self : Formatter,
max_indent~ : Int,
margin~ : Int,
) -> Unit raise InvalidArgument {
let g = { max_indent, margin, }
match validate_geometry(g) {
Some(msg) => raise InvalidArgument("Format.pp_set_geometry: " + msg)
None => self.set_full_geometry(g)
}
}
///|
/// Like `set_geometry`, but does nothing if the geometry is invalid.
pub fn Formatter::safe_set_geometry(
self : Formatter,
max_indent~ : Int,
margin~ : Int,
) -> Unit {
let g = { max_indent, margin, }
if validate_geometry(g) is None {
self.set_full_geometry(g)
}
}
///|
/// The geometry of the formatter.
pub fn Formatter::get_geometry(self : Formatter) -> Geometry {
{ margin: self.margin.to_int(), max_indent: self.max_indent.to_int(), }
}
///|
/// Update the geometry of the formatter.
pub fn Formatter::update_geometry(
self : Formatter,
update : (Geometry) -> Geometry,
) -> Unit {
self.set_full_geometry(update(self.get_geometry()))
}
// Output functions
///|
/// Set the output functions of the formatter.
pub fn Formatter::set_out_functions(
self : Formatter,
out : OutFunctions,
) -> Unit {
self.out = out
}
///|
/// The output functions of the formatter.
pub fn Formatter::get_out_functions(self : Formatter) -> OutFunctions {
self.out
}
///|
/// Set the functions that output a string and flush the output, like
/// OCaml's `pp_set_formatter_output_functions`: the other output functions
/// are unchanged (those of `Formatter(output)` output newlines and spaces
/// with the current output function).
pub fn Formatter::set_output_functions(
self : Formatter,
out_string : (StringView) -> Unit,
out_flush : () -> Unit,
) -> Unit {
self.out = { ..self.out, out_string, out_flush, }
}
///|
/// The functions that output a string and flush the output, like OCaml's
/// `pp_get_formatter_output_functions`.
pub fn Formatter::get_output_functions(
self : Formatter,
) -> ((StringView) -> Unit, () -> Unit) {
(self.out.out_string, self.out.out_flush)
}
// Tags
///|
/// Set the tag functions of the formatter.
pub fn Formatter::set_stag_functions(
self : Formatter,
funs : StagFunctions,
) -> Unit {
self.stag_functions = funs
}
///|
/// The tag functions of the formatter.
pub fn Formatter::get_stag_functions(self : Formatter) -> StagFunctions {
self.stag_functions
}
///|
/// Whether to call the `print_*_stag` functions on tags.
pub fn Formatter::set_print_tags(self : Formatter, b : Bool) -> Unit {
self.print_tags = b
}
///|
/// Whether to output the `mark_*_stag` markers of tags.
pub fn Formatter::set_mark_tags(self : Formatter, b : Bool) -> Unit {
self.mark_tags = b
}
///|
/// Whether the `print_*_stag` functions are called on tags.
pub fn Formatter::get_print_tags(self : Formatter) -> Bool {
self.print_tags
}
///|
/// Whether the `mark_*_stag` markers of tags are output.
pub fn Formatter::get_mark_tags(self : Formatter) -> Bool {
self.mark_tags
}
///|
/// Set both `print_tags` and `mark_tags`.
pub fn Formatter::set_tags(self : Formatter, b : Bool) -> Unit {
self.set_print_tags(b)
self.set_mark_tags(b)
}
// Symbolic output
///|
/// An item of symbolic output.
pub(all) enum SymbolicOutputItem {
OutputFlush
OutputNewline
OutputString(String)
OutputSpaces(Int)
OutputIndent(Int)
} derive(Eq, Debug)
///|
pub extend SymbolicOutputItem with Eq::{equal, not_equal}
///|
pub extend SymbolicOutputItem with Debug::{to_repr}
///|
/// A buffer of symbolic output: pretty-printing with no low-level output,
/// so that the output can be post-processed.
pub struct SymbolicOutputBuffer {
priv items : Array[SymbolicOutputItem]
}
///|
/// Create a buffer of symbolic output.
pub fn SymbolicOutputBuffer::SymbolicOutputBuffer() -> SymbolicOutputBuffer {
{ items: [], }
}
///|
/// Remove the contents of the buffer.
pub fn SymbolicOutputBuffer::clear(self : SymbolicOutputBuffer) -> Unit {
self.items.clear()
}
///|
/// The contents of the buffer.
pub fn SymbolicOutputBuffer::get(
self : SymbolicOutputBuffer,
) -> Array[SymbolicOutputItem] {
self.items.copy()
}
///|
/// Return the contents of the buffer and clear it.
pub fn SymbolicOutputBuffer::flush(
self : SymbolicOutputBuffer,
) -> Array[SymbolicOutputItem] {
let items = self.items.copy()
self.items.clear()
items
}
///|
/// Add an item to the buffer.
pub fn SymbolicOutputBuffer::add(
self : SymbolicOutputBuffer,
item : SymbolicOutputItem,
) -> Unit {
self.items.push(item)
}
///|
/// Create a formatter whose output is stored in a symbolic output buffer.
pub fn Formatter::of_symbolic_output_buffer(
sob : SymbolicOutputBuffer,
) -> Formatter {
Formatter::of_out_functions({
out_string: s => sob.add(OutputString(s.to_owned())),
out_flush: () => sob.add(OutputFlush),
out_newline: () => sob.add(OutputNewline),
out_spaces: n => sob.add(OutputSpaces(n)),
out_indent: n => sob.add(OutputIndent(n)),
})
}