#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Doc {
Nil,
Text(String),
Line,
SoftLine,
HardLine,
Indent(Box<Doc>),
Concat(Box<Doc>, Box<Doc>),
Group(Box<Doc>),
IfBreak { flat: Box<Doc>, broken: Box<Doc> },
}
impl Doc {
pub fn text(s: impl Into<String>) -> Doc {
let s = s.into();
if s.is_empty() { Doc::Nil } else { Doc::Text(s) }
}
pub fn line() -> Doc {
Doc::Line
}
pub fn softline() -> Doc {
Doc::SoftLine
}
pub fn hardline() -> Doc {
Doc::HardLine
}
pub fn indent(doc: Doc) -> Doc {
if matches!(doc, Doc::Nil) {
Doc::Nil
} else {
Doc::Indent(Box::new(doc))
}
}
pub fn group(doc: Doc) -> Doc {
if matches!(doc, Doc::Nil) {
Doc::Nil
} else {
Doc::Group(Box::new(doc))
}
}
pub fn concat(self, other: Doc) -> Doc {
match (self, other) {
(Doc::Nil, other) => other,
(this, Doc::Nil) => this,
(this, other) => Doc::Concat(Box::new(this), Box::new(other)),
}
}
pub fn if_break(flat: Doc, broken: Doc) -> Doc {
Doc::IfBreak {
flat: Box::new(flat),
broken: Box::new(broken),
}
}
pub fn join(docs: impl IntoIterator<Item = Doc>, sep: Doc) -> Doc {
let mut result = Doc::Nil;
let mut first = true;
for doc in docs {
if first {
first = false;
result = doc;
} else {
result = result.concat(sep.clone()).concat(doc);
}
}
result
}
pub fn concat_all(docs: impl IntoIterator<Item = Doc>) -> Doc {
let mut result = Doc::Nil;
for doc in docs {
result = result.concat(doc);
}
result
}
pub fn surround(prefix: Doc, content: Doc, suffix: Doc) -> Doc {
prefix.concat(content).concat(suffix)
}
}
#[derive(Debug, Default)]
pub struct DocBuilder {
parts: Vec<Doc>,
}
impl DocBuilder {
pub fn new() -> Self {
Self { parts: Vec::new() }
}
pub fn text(&mut self, s: impl Into<String>) -> &mut Self {
self.parts.push(Doc::text(s));
self
}
pub fn line(&mut self) -> &mut Self {
self.parts.push(Doc::line());
self
}
pub fn softline(&mut self) -> &mut Self {
self.parts.push(Doc::softline());
self
}
pub fn hardline(&mut self) -> &mut Self {
self.parts.push(Doc::hardline());
self
}
pub fn push(&mut self, doc: Doc) -> &mut Self {
self.parts.push(doc);
self
}
pub fn build(self) -> Doc {
Doc::concat_all(self.parts)
}
pub fn build_group(self) -> Doc {
Doc::group(self.build())
}
pub fn build_indent(self) -> Doc {
Doc::indent(self.build())
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_text_empty() {
assert_eq!(Doc::text(""), Doc::Nil);
}
#[test]
fn test_text_non_empty() {
assert_eq!(Doc::text("hello"), Doc::Text("hello".to_string()));
}
#[test]
fn test_concat_nil() {
let doc = Doc::text("a").concat(Doc::Nil);
assert_eq!(doc, Doc::Text("a".to_string()));
let doc = Doc::Nil.concat(Doc::text("b"));
assert_eq!(doc, Doc::Text("b".to_string()));
}
#[test]
fn test_join() {
let docs = vec![Doc::text("a"), Doc::text("b"), Doc::text("c")];
let joined = Doc::join(docs, Doc::text(", "));
assert!(matches!(joined, Doc::Concat(_, _)));
}
#[test]
fn test_builder() {
let doc = {
let mut b = DocBuilder::new();
b.text("hello").line().text("world");
b.build()
};
assert!(matches!(doc, Doc::Concat(_, _)));
}
}