///| Empty document.
/// `render(empty) == ""`
pub let empty : Document = Empty
///| Force line break with current indentation.
/// This document will always doesn't fit in the current line.
///
/// # Example
///
/// ```
/// render(text("hello") + nest(hardline + text("world")))
/// |> inspect!(
/// content=
/// #|hello
/// #| world
/// ,
/// )
/// ```
///
pub let hardline : Document = Line
///| Space character.
/// `render(space) == " "`
pub let space : Document = char(' ')
///| Represents a line break if the line is too long, or nothing if it fits.
pub let softline : Document = switch(empty, line)
///| Represents a line break if the line is too long, or a space if it fits.
pub let line : Document = switch(space, hardline)
///|
pub fn text(s : String) -> Document {
Text(s)
}
///|
pub fn char(c : Char) -> Document {
Text(c.to_string())
}
///| Concatenate documents.
pub fn list(ls : Array[Document]) -> Document {
fn aux {
([] : ArrayView[_]) => Empty
[x, y] => concat(x, y)
[x, .. xs] => concat(x, aux(xs))
}
aux(ls[:])
}
///| `group(doc)` tries to render the `doc` on a single line,
/// but if the `doc` doesn't fit, or contains a `hardline`, all the `switch`
/// inside the `doc` will be rendered with the right document.
pub fn group(doc : Document) -> Document {
Group(requirement(doc), doc)
}
///| Switch between two documents based on the available space.
/// The left document represents the flatten mode, and the right one the pretty mode.
/// If the left document fits, it will be rendered, otherwise the right one.
pub fn switch(l : Document, r : Document) -> Document {
Switch(requirement(l), l, r)
}
///| Nest the document by `n` spaces.
/// Any line breaks inside the nested document will be indented by `n` spaces.
pub fn nest(indent~ : Int = 2, doc : Document) -> Document {
Nest(requirement(doc), indent, doc)
}
///| Concatenate two documents.
/// You can also use the `+` operator.
pub fn concat(l : Document, r : Document) -> Document {
Concat(requirement(l) + requirement(r), l, r)
}
///|
pub impl Add for Document with op_add(self, other) {
concat(self, other)
}
///|
pub fn to_string(self : Document) -> String {
render_document(80, self)
}
///|
pub impl Show for Document with output(self, buf) {
buf.write_string(render_document(80, self))
}
///|
fn render_document(width : Int, doc : Document) -> String {
let buf = @buffer.new()
let mut column = 0
fn aux(indent, flat_mode, doc) {
match doc {
Empty => ()
Text(s) => {
buf.write_string(s)
column += s.length()
}
Concat(_, l, r) => {
aux(indent, flat_mode, l)
aux(indent, flat_mode, r)
}
Switch(_, l, r) =>
if flat_mode {
aux(indent, flat_mode, l)
} else {
aux(indent, flat_mode, r)
}
Line => {
buf.write_char('\n')
for i = 0; i < indent; i = i + 1 {
buf.write_char(' ')
}
column = indent
}
Nest(_, i, d) => aux(indent + i, flat_mode, d)
Group(req, d) => {
let flat_mode = Space(column) + req <= Space(width)
aux(indent, flat_mode, d)
}
}
}
aux(0, true, doc)
buf.contents().to_unchecked_string()
}