use std::rc::Rc;
use once_cell::unsync::Lazy;
mod tests;
pub trait ToDoc {
fn to_doc(&self) -> Doc;
fn render(&self, width: i16) -> String {
self.to_doc().render(width)
}
}
pub fn to_list<'a, T>(docs: impl IntoIterator<Item = &'a T>, separator: Doc) -> Doc
where
T: ToDoc + 'a,
{
let mut iter = docs.into_iter();
if let Some(first) = iter.next() {
let mut output = first.to_doc();
for next in iter {
output = output.concat(separator.clone()).concat(next.to_doc());
}
output
} else {
Doc::nil()
}
}
pub struct Doc(Rc<DocInner>);
type DocFn = Rc<dyn Fn(i16) -> Doc + 'static>;
enum DocInner {
Empty,
Text(String),
Line, Concat(Doc, Doc),
Nest(i16, Doc),
Alt(Doc, Doc),
Nesting(DocFn),
Column(DocFn),
}
impl DocInner {
fn into_doc(self) -> Doc {
Doc(Rc::new(self))
}
}
impl Clone for Doc {
fn clone(&self) -> Self {
Doc(Rc::clone(&self.0))
}
}
thread_local! {
static NIL_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Empty));
static SPACE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text(" ".to_string())));
static COMMA_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text(",".to_string())));
static LINE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Line));
static SOFTLINE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Alt(Doc::space(), Doc::line())));
static SOFTLINE_EMPTY_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Alt(Doc::nil(), Doc::line())));
static LPAREN_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text("(".to_string())));
static RPAREN_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text(")".to_string())));
static LANGLE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text("<".to_string())));
static RANGLE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text(">".to_string())));
static LBRACKET_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text("[".to_string())));
static RBRACKET_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text("]".to_string())));
static LBRACE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text("{".to_string())));
static RBRACE_INNER: Lazy<Rc<DocInner>> = Lazy::new(|| Rc::new(DocInner::Text("}".to_string())));
}
impl Doc {
pub fn nil() -> Doc {
NIL_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn space() -> Doc {
SPACE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn comma() -> Doc {
COMMA_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn line() -> Doc {
LINE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn softline() -> Doc {
SOFTLINE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn softline_empty() -> Doc {
SOFTLINE_EMPTY_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn text<S: Into<String>>(str: S) -> Doc {
DocInner::Text(str.into()).into_doc()
}
pub fn concat(self, other: Doc) -> Doc {
DocInner::Concat(self, other).into_doc()
}
pub fn nest(self, depth: i16) -> Doc {
DocInner::Nest(depth, self).into_doc()
}
pub fn concat_space(self, other: Doc) -> Doc {
self.concat(Doc::space()).concat(other)
}
pub fn alt(self, other: Doc) -> Doc {
DocInner::Alt(self, other).into_doc()
}
pub fn group(self) -> Doc {
match &*self.0 {
DocInner::Alt(_, _) => self,
_ => DocInner::Alt(self.clone().flatten(), self).into_doc(),
}
}
fn flatten(self) -> Doc {
match &*self.0 {
DocInner::Empty | DocInner::Text(_) => self,
DocInner::Line => Doc::space(),
DocInner::Concat(x, y) => {
DocInner::Concat(x.clone().flatten(), y.clone().flatten()).into_doc()
}
DocInner::Nest(_, inner) => inner.clone().flatten(),
DocInner::Alt(flat, _) => flat.clone().flatten(),
DocInner::Column(f) => {
let f = Rc::clone(f);
let f = Rc::new(move |i| f(i).flatten());
Doc(Rc::new(DocInner::Column(f)))
}
DocInner::Nesting(f) => {
let f = Rc::clone(f);
let f = Rc::new(move |i| f(i).flatten());
Doc(Rc::new(DocInner::Nesting(f)))
}
}
}
pub fn column<F>(f: F) -> Doc
where
F: Fn(i16) -> Doc + 'static,
{
let f: DocFn = Rc::new(f);
DocInner::Column(f).into_doc()
}
pub fn nesting<F>(f: F) -> Doc
where
F: Fn(i16) -> Doc + 'static,
{
let f: DocFn = Rc::new(f);
DocInner::Nesting(f).into_doc()
}
pub fn concat_with<F>(docs: impl IntoIterator<Item = Doc>, concat_f: F) -> Doc
where
F: Fn(Doc, Doc) -> Doc,
{
let mut iter = docs.into_iter();
if let Some(first) = iter.next() {
let mut output = first;
for next in iter {
output = concat_f(output, next);
}
output
} else {
Doc::nil()
}
}
pub fn hang(self, i: i16) -> Doc {
self.nest(i).align()
}
pub fn indent(self, i: i16) -> Doc {
Doc::spaces(i).concat(self).hang(i)
}
pub fn align(self) -> Doc {
Doc::column({
let base = self.clone();
move |k| {
let base2 = base.clone();
Doc::nesting(move |i| base2.clone().nest(k - i))
}
})
}
pub fn spaces(i: i16) -> Doc {
match i {
0 => Doc::nil(),
1 => Doc::space(),
n => Doc::text(" ".repeat(n as usize)),
}
}
pub fn hsep(docs: impl IntoIterator<Item = Doc>) -> Doc {
Doc::concat_with(docs, |x, y| x.concat_space(y))
}
pub fn vsep(docs: impl IntoIterator<Item = Doc>) -> Doc {
Doc::concat_with(docs, |x, y| x.concat(Doc::line()).concat(y))
}
pub fn sep(docs: impl IntoIterator<Item = Doc>) -> Doc {
Doc::vsep(docs).group()
}
pub fn hcat(docs: impl IntoIterator<Item = Doc>) -> Doc {
Doc::concat_with(docs, |x, y| x.concat(y))
}
pub fn intersperse(docs: impl IntoIterator<Item = Doc>, separator: Doc) -> Doc {
let mut iter = docs.into_iter();
if let Some(first) = iter.next() {
let mut output = first;
for next in iter {
output = output.concat(separator.clone()).concat(next);
}
output
} else {
Doc::nil()
}
}
pub fn parens(self) -> Doc {
Self::lparen().concat(self).concat(Self::rparen())
}
pub fn angles(self) -> Doc {
Self::langle().concat(self).concat(Self::rangle())
}
pub fn brackets(self) -> Doc {
Self::lbracket().concat(self).concat(Self::rbracket())
}
pub fn braces(self) -> Doc {
Self::lbrace().concat(self).concat(Self::rbrace())
}
pub fn block(self, start: Doc, end: Doc) -> Doc {
start
.concat(Doc::line())
.concat(self.indent(4).group())
.concat(Doc::line())
.concat(end)
}
pub fn fill(xs: &[Doc]) -> Doc {
Self::fill_core(xs, 0, false)
}
fn fill_core(xs: &[Doc], i: usize, head_flat: bool) -> Doc {
if i >= xs.len() {
return Doc::nil();
}
let n = xs.len() - i;
if n == 1 {
let mut d = xs[i].clone();
if head_flat {
d = d.flatten();
}
return d;
}
let x = xs[i].clone();
let y_is_head = i + 1;
let x_flat = if head_flat {
x.clone()
} else {
x.clone().flatten()
};
let left = x_flat
.concat(Doc::space())
.concat(Self::fill_core(xs, y_is_head, true));
let x_for_right = if head_flat { x } else { xs[i].clone() };
let right = x_for_right
.concat(Doc::line())
.concat(Self::fill_core(xs, y_is_head, false));
left.alt(right)
}
pub fn lparen() -> Doc {
LPAREN_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn rparen() -> Doc {
RPAREN_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn langle() -> Doc {
LANGLE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn rangle() -> Doc {
RANGLE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn lbracket() -> Doc {
LBRACKET_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn rbracket() -> Doc {
RBRACKET_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn lbrace() -> Doc {
LBRACE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn rbrace() -> Doc {
RBRACE_INNER.with(|lazy| Doc(Rc::clone(lazy)))
}
pub fn render(self, width: i16) -> String {
let rendered = self.best(width);
let output = rendered.render();
output.unwrap()
}
fn best(self, width: i16) -> Render {
use DocInner as DI;
enum Cons {
Cell { head: (i16, Doc), tail: Rc<Cons> },
Nil,
}
fn cons(head: (i16, Doc), tail: Rc<Cons>) -> Rc<Cons> {
Rc::new(Cons::Cell { head, tail })
}
fn fits(mut remaining: i16, mut cursor: i16, mut docs: Rc<Cons>) -> bool {
while let Cons::Cell {
head: (i, doc),
tail,
} = &*docs
{
match &*doc.0 {
DI::Line => return true,
DI::Empty => {
docs = tail.clone();
}
DI::Text(s) => {
let s_len = s.len() as i16;
if s_len > remaining {
return false;
};
remaining -= s_len;
let Some(new_cursor) = cursor.checked_add(s_len) else {
return false;
};
cursor = new_cursor;
docs = tail.clone();
}
DI::Concat(x, y) => {
docs = cons((*i, x.clone()), cons((*i, y.clone()), tail.clone()));
}
DI::Nest(j, inner) => {
docs = cons((i + j, inner.clone()), tail.clone());
}
DI::Alt(flat, _doc2) => {
docs = cons((*i, flat.clone()), tail.clone());
}
DI::Column(f) => {
docs = cons((*i, f(cursor)), tail.clone());
}
DI::Nesting(f) => {
docs = cons((*i, f(*i)), tail.clone());
}
}
}
true
}
let mut docs = cons((0, self), Rc::new(Cons::Nil));
let mut cursor = 0i16;
let mut out: Vec<RenderPart> = vec![];
while let Cons::Cell { head, tail } = &*docs {
let (indent, doc) = head;
match &*doc.0 {
DI::Empty => {
docs = tail.clone();
}
DI::Text(s) => {
out.push(RenderPart::Text(s.to_string()));
cursor = cursor.saturating_add(s.len() as i16);
docs = tail.clone();
}
DI::Concat(x, y) => {
docs = cons(
(*indent, x.clone()),
cons((*indent, y.clone()), tail.clone()),
);
}
DI::Nest(j, inner) => {
docs = cons((indent + j, inner.clone()), tail.clone());
}
DI::Line => {
out.push(RenderPart::Line(*indent));
cursor = *indent;
docs = tail.clone();
}
DI::Alt(flat, alt) => {
let flat = cons((*indent, flat.clone()), tail.clone());
if fits(width, cursor, flat.clone()) {
docs = flat;
} else {
docs = cons((*indent, alt.clone()), tail.clone());
}
}
DI::Column(f) => {
docs = cons((*indent, f(cursor)), tail.clone());
}
DI::Nesting(f) => {
docs = cons((*indent, f(*indent)), tail.clone());
}
}
}
Render(out)
}
}
enum RenderPart {
Line(i16),
Text(String),
}
struct Render(Vec<RenderPart>);
impl Render {
fn render(&self) -> Result<String, std::fmt::Error> {
use std::fmt::Write;
let renders = &self.0;
let mut output = String::new();
for render in renders.iter() {
match render {
RenderPart::Line(i) => {
writeln!(&mut output)?;
for _n in 0..*i {
write!(&mut output, " ")?;
}
}
RenderPart::Text(s) => {
write!(&mut output, "{}", s)?;
}
}
}
Ok(output)
}
}