use super::config::FormatConfig;
#[derive(Debug, Clone)]
pub enum Doc {
Nil,
Text(String),
Line,
HardLine,
SoftLine,
Concat(Vec<Doc>),
Group(Box<Doc>),
Nest(usize, Box<Doc>),
Align(Box<Doc>),
Fill(Vec<Doc>),
IfFlat(Box<Doc>, Box<Doc>),
LineSuffix(String),
}
impl Doc {
pub fn text(s: impl Into<String>) -> Self {
Doc::Text(s.into())
}
pub fn line() -> Self {
Doc::Line
}
pub fn hard_line() -> Self {
Doc::HardLine
}
pub fn soft_line() -> Self {
Doc::SoftLine
}
pub fn concat(docs: Vec<Doc>) -> Self {
Doc::Concat(docs)
}
pub fn group(doc: Doc) -> Self {
Doc::Group(Box::new(doc))
}
pub fn nest(indent: usize, doc: Doc) -> Self {
Doc::Nest(indent, Box::new(doc))
}
pub fn align(doc: Doc) -> Self {
Doc::Align(Box::new(doc))
}
pub fn fill(docs: Vec<Doc>) -> Self {
Doc::Fill(docs)
}
pub fn if_flat(flat: Doc, break_: Doc) -> Self {
Doc::IfFlat(Box::new(flat), Box::new(break_))
}
pub fn join(docs: Vec<Doc>, sep: Doc) -> Self {
let mut result = Vec::new();
for (i, doc) in docs.into_iter().enumerate() {
if i > 0 {
result.push(sep.clone());
}
result.push(doc);
}
Doc::concat(result)
}
pub fn intersperse(docs: Vec<Doc>, sep: Doc) -> Self {
Self::join(docs, sep)
}
pub fn brackets(left: &str, doc: Doc, right: &str) -> Self {
Doc::concat(vec![Doc::text(left), doc, Doc::text(right)])
}
pub fn parens(doc: Doc) -> Self {
Self::brackets("(", doc, ")")
}
pub fn square(doc: Doc) -> Self {
Self::brackets("[", doc, "]")
}
pub fn braces(doc: Doc) -> Self {
Self::brackets("{", doc, "}")
}
}
pub struct PrettyPrinter {
config: FormatConfig,
output: String,
column: usize,
indent: usize,
line_suffixes: Vec<String>,
}
impl PrettyPrinter {
pub fn new(config: FormatConfig) -> Self {
Self {
config,
output: String::new(),
column: 0,
indent: 0,
line_suffixes: Vec::new(),
}
}
pub fn print(&mut self, doc: &Doc) -> String {
self.output.clear();
self.column = 0;
self.indent = 0;
self.line_suffixes.clear();
let fits = self.fits(doc, self.config.max_line_length, true);
self.print_doc(doc, fits);
self.flush_line_suffixes();
if self.config.final_newline && !self.output.ends_with('\n') {
self.output.push_str(self.config.newline_str());
}
std::mem::take(&mut self.output)
}
fn fits(&self, doc: &Doc, width: usize, flat: bool) -> bool {
self.fits_inner(doc, width as isize, flat) >= 0
}
fn fits_inner(&self, doc: &Doc, mut width: isize, flat: bool) -> isize {
match doc {
Doc::Nil => width,
Doc::Text(s) => width - s.len() as isize,
Doc::Line => {
if flat {
width - 1 } else {
width }
}
Doc::HardLine => width, Doc::SoftLine => width, Doc::Concat(docs) => {
for d in docs {
width = self.fits_inner(d, width, flat);
if width < 0 {
return width;
}
}
width
}
Doc::Group(d) => self.fits_inner(d, width, true),
Doc::Nest(_, d) => self.fits_inner(d, width, flat),
Doc::Align(d) => self.fits_inner(d, width, flat),
Doc::Fill(docs) => {
for d in docs {
width = self.fits_inner(d, width, flat);
if width < 0 {
return width;
}
}
width
}
Doc::IfFlat(f, b) => {
if flat {
self.fits_inner(f, width, flat)
} else {
self.fits_inner(b, width, flat)
}
}
Doc::LineSuffix(_) => width,
}
}
fn print_doc(&mut self, doc: &Doc, flat: bool) {
match doc {
Doc::Nil => {}
Doc::Text(s) => {
self.output.push_str(s);
self.column += s.len();
}
Doc::Line => {
if flat {
self.output.push(' ');
self.column += 1;
} else {
self.print_newline();
}
}
Doc::HardLine => {
self.print_newline();
}
Doc::SoftLine => {
if !flat {
self.print_newline();
}
}
Doc::Concat(docs) => {
for d in docs {
self.print_doc(d, flat);
}
}
Doc::Group(d) => {
let group_flat =
flat || self.fits(d, self.config.max_line_length - self.column, true);
self.print_doc(d, group_flat);
}
Doc::Nest(n, d) => {
self.indent += n;
self.print_doc(d, flat);
self.indent -= n;
}
Doc::Align(d) => {
let old_indent = self.indent;
self.indent = self.column;
self.print_doc(d, flat);
self.indent = old_indent;
}
Doc::Fill(docs) => {
for (i, d) in docs.iter().enumerate() {
if i > 0 {
if self.column + self.doc_width(d) > self.config.max_line_length {
self.print_newline();
} else {
self.output.push(' ');
self.column += 1;
}
}
self.print_doc(d, true);
}
}
Doc::IfFlat(f, b) => {
if flat {
self.print_doc(f, flat);
} else {
self.print_doc(b, flat);
}
}
Doc::LineSuffix(s) => {
self.line_suffixes.push(s.clone());
}
}
}
fn print_newline(&mut self) {
self.flush_line_suffixes();
if self.config.trim_trailing_whitespace {
while self.output.ends_with(' ') || self.output.ends_with('\t') {
self.output.pop();
}
}
self.output.push_str(self.config.newline_str());
self.output.push_str(
&self
.config
.indent_at(self.indent / self.config.indent_width),
);
self.column = self.indent;
}
fn flush_line_suffixes(&mut self) {
for suffix in std::mem::take(&mut self.line_suffixes) {
self.output.push_str(&suffix);
}
}
fn doc_width(&self, doc: &Doc) -> usize {
match doc {
Doc::Nil => 0,
Doc::Text(s) => s.len(),
Doc::Line | Doc::HardLine | Doc::SoftLine => 0,
Doc::Concat(docs) => docs.iter().map(|d| self.doc_width(d)).sum(),
Doc::Group(d) | Doc::Nest(_, d) | Doc::Align(d) => self.doc_width(d),
Doc::Fill(docs) => docs.iter().map(|d| self.doc_width(d)).sum(),
Doc::IfFlat(f, _) => self.doc_width(f),
Doc::LineSuffix(s) => s.len(),
}
}
}
pub struct DocBuilder {
docs: Vec<Doc>,
}
impl DocBuilder {
pub fn new() -> Self {
Self { docs: Vec::new() }
}
pub fn text(mut self, s: impl Into<String>) -> Self {
self.docs.push(Doc::text(s));
self
}
pub fn line(mut self) -> Self {
self.docs.push(Doc::line());
self
}
pub fn hard_line(mut self) -> Self {
self.docs.push(Doc::hard_line());
self
}
pub fn soft_line(mut self) -> Self {
self.docs.push(Doc::soft_line());
self
}
pub fn space(mut self) -> Self {
self.docs.push(Doc::text(" "));
self
}
pub fn doc(mut self, doc: Doc) -> Self {
self.docs.push(doc);
self
}
pub fn nest(mut self, indent: usize, f: impl FnOnce(DocBuilder) -> DocBuilder) -> Self {
let nested = f(DocBuilder::new()).build();
self.docs.push(Doc::nest(indent, nested));
self
}
pub fn group(mut self, f: impl FnOnce(DocBuilder) -> DocBuilder) -> Self {
let grouped = f(DocBuilder::new()).build();
self.docs.push(Doc::group(grouped));
self
}
pub fn build(self) -> Doc {
Doc::concat(self.docs)
}
}
impl Default for DocBuilder {
fn default() -> Self {
Self::new()
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_simple_text() {
let config = FormatConfig::default();
let mut printer = PrettyPrinter::new(config);
let doc = Doc::text("hello world");
let result = printer.print(&doc);
assert_eq!(result.trim(), "hello world");
}
#[test]
fn test_concat() {
let config = FormatConfig::default();
let mut printer = PrettyPrinter::new(config);
let doc = Doc::concat(vec![Doc::text("hello"), Doc::text(" "), Doc::text("world")]);
let result = printer.print(&doc);
assert_eq!(result.trim(), "hello world");
}
#[test]
fn test_group_fits() {
let config = FormatConfig::default();
let mut printer = PrettyPrinter::new(config);
let doc = Doc::group(Doc::concat(vec![
Doc::text("fn"),
Doc::text("("),
Doc::text("x"),
Doc::text(")"),
]));
let result = printer.print(&doc);
assert_eq!(result.trim(), "fn(x)");
}
#[test]
fn test_nest() {
let mut config = FormatConfig::default();
config.final_newline = false;
let mut printer = PrettyPrinter::new(config);
let doc = Doc::concat(vec![
Doc::text("{"),
Doc::nest(4, Doc::concat(vec![Doc::hard_line(), Doc::text("body")])),
Doc::hard_line(),
Doc::text("}"),
]);
let result = printer.print(&doc);
assert!(result.contains(" body"));
}
}