// Adapted from https://github.com/rust-lang/rust/blob/1.57.0/compiler/rustc_ast_pretty/src/pp.rs. // See "Algorithm notes" in the crate-level rustdoc. use crate::ring::RingBuffer; use crate::{MARGIN, MIN_SPACE}; use std::borrow::Cow; use std::cmp; use std::collections::VecDeque; use std::iter; #[derive(Clone, Copy, PartialEq)] pub enum Breaks { Consistent, Inconsistent, } #[derive(Clone, Copy, Default)] pub struct BreakToken { pub offset: isize, pub blank_space: usize, pub pre_break: Option, pub post_break: Option, pub no_break: Option, pub if_nonempty: bool, pub never_break: bool, } #[derive(Clone, Copy)] pub struct BeginToken { pub offset: isize, pub breaks: Breaks, } #[derive(Clone)] pub enum Token { String(Cow<'static, str>), Break(BreakToken), Begin(BeginToken), End, } #[derive(Copy, Clone)] enum PrintFrame { Fits(Breaks), Broken(usize, Breaks), } pub const SIZE_INFINITY: isize = 0xffff; pub struct Printer { out: String, // Number of spaces left on line space: isize, // Ring-buffer of tokens and calculated sizes buf: RingBuffer, // Total size of tokens already printed left_total: isize, // Total size of tokens enqueued, including printed and not yet printed right_total: isize, // Holds the ring-buffer index of the Begin that started the current block, // possibly with the most recent Break after that Begin (if there is any) on // top of it. Values are pushed and popped on the back of the queue using it // like stack, and elsewhere old values are popped from the front of the // queue as they become irrelevant due to the primary ring-buffer advancing. scan_stack: VecDeque, // Stack of blocks-in-progress being flushed by print print_stack: Vec, // Level of indentation of current line indent: usize, // Buffered indentation to avoid writing trailing whitespace pending_indentation: usize, } #[derive(Clone)] struct BufEntry { token: Token, size: isize, } impl Printer { pub fn new() -> Self { Printer { out: String::new(), space: MARGIN, buf: RingBuffer::new(), left_total: 0, right_total: 0, scan_stack: VecDeque::new(), print_stack: Vec::new(), indent: 0, pending_indentation: 0, } } pub fn eof(mut self) -> String { if !self.scan_stack.is_empty() { self.check_stack(0); self.advance_left(); } self.out } pub fn scan_begin(&mut self, token: BeginToken) { if self.scan_stack.is_empty() { self.left_total = 1; self.right_total = 1; self.buf.clear(); } let right = self.buf.push(BufEntry { token: Token::Begin(token), size: -self.right_total, }); self.scan_stack.push_back(right); } pub fn scan_end(&mut self) { if self.scan_stack.is_empty() { self.print_end(); } else { if !self.buf.is_empty() { if let Token::Break(break_token) = self.buf.last().token { if self.buf.len() >= 2 { if let Token::Begin(_) = self.buf.second_last().token { self.buf.pop_last(); self.buf.pop_last(); self.scan_stack.pop_back(); self.scan_stack.pop_back(); self.right_total -= break_token.blank_space as isize; return; } } if break_token.if_nonempty { self.buf.pop_last(); self.scan_stack.pop_back(); self.right_total -= break_token.blank_space as isize; } } } let right = self.buf.push(BufEntry { token: Token::End, size: -1, }); self.scan_stack.push_back(right); } } pub fn scan_break(&mut self, token: BreakToken) { if self.scan_stack.is_empty() { self.left_total = 1; self.right_total = 1; self.buf.clear(); } else { self.check_stack(0); } let right = self.buf.push(BufEntry { token: Token::Break(token), size: -self.right_total, }); self.scan_stack.push_back(right); self.right_total += token.blank_space as isize; } pub fn scan_string(&mut self, string: Cow<'static, str>) { if self.scan_stack.is_empty() { self.print_string(string); } else { let len = string.len() as isize; self.buf.push(BufEntry { token: Token::String(string), size: len, }); self.right_total += len; self.check_stream(); } } pub fn offset(&mut self, offset: isize) { match &mut self.buf.last_mut().token { Token::Break(token) => token.offset += offset, Token::Begin(_) => {} Token::String(_) | Token::End => unreachable!(), } } pub fn end_with_max_width(&mut self, max: isize) { let mut depth = 1; for &index in self.scan_stack.iter().rev() { let entry = &self.buf[index]; match entry.token { Token::Begin(_) => { depth -= 1; if depth == 0 { if entry.size < 0 { let actual_width = entry.size + self.right_total; if actual_width > max { self.buf.push(BufEntry { token: Token::String(Cow::Borrowed("")), size: SIZE_INFINITY, }); self.right_total += SIZE_INFINITY; } } break; } } Token::End => depth += 1, Token::Break(_) => {} Token::String(_) => unreachable!(), } } self.scan_end(); } fn check_stream(&mut self) { while self.right_total - self.left_total > self.space { if *self.scan_stack.front().unwrap() == self.buf.index_of_first() { self.scan_stack.pop_front().unwrap(); self.buf.first_mut().size = SIZE_INFINITY; } self.advance_left(); if self.buf.is_empty() { break; } } } fn advance_left(&mut self) { while self.buf.first().size >= 0 { let left = self.buf.pop_first(); match left.token { Token::String(string) => { self.left_total += left.size; self.print_string(string); } Token::Break(token) => { self.left_total += token.blank_space as isize; self.print_break(token, left.size); } Token::Begin(token) => self.print_begin(token, left.size), Token::End => self.print_end(), } if self.buf.is_empty() { break; } } } fn check_stack(&mut self, mut depth: usize) { while let Some(&index) = self.scan_stack.back() { let entry = &mut self.buf[index]; match entry.token { Token::Begin(_) => { if depth == 0 { break; } self.scan_stack.pop_back().unwrap(); entry.size += self.right_total; depth -= 1; } Token::End => { self.scan_stack.pop_back().unwrap(); entry.size = 1; depth += 1; } Token::Break(_) => { self.scan_stack.pop_back().unwrap(); entry.size += self.right_total; if depth == 0 { break; } } Token::String(_) => unreachable!(), } } } fn get_top(&self) -> PrintFrame { const OUTER: PrintFrame = PrintFrame::Broken(0, Breaks::Inconsistent); self.print_stack.last().map_or(OUTER, PrintFrame::clone) } fn print_begin(&mut self, token: BeginToken, size: isize) { if cfg!(prettyplease_debug) { self.out.push(match token.breaks { Breaks::Consistent => '«', Breaks::Inconsistent => '‹', }); if cfg!(prettyplease_debug_indent) { self.out .extend(token.offset.to_string().chars().map(|ch| match ch { '0'..='9' => ['₀', '₁', '₂', '₃', '₄', '₅', '₆', '₇', '₈', '₉'] [(ch as u8 - b'0') as usize], '-' => '₋', _ => unreachable!(), })); } } if size > self.space { self.print_stack .push(PrintFrame::Broken(self.indent, token.breaks)); self.indent = usize::try_from(self.indent as isize + token.offset).unwrap(); } else { self.print_stack.push(PrintFrame::Fits(token.breaks)); } } fn print_end(&mut self) { let breaks = match self.print_stack.pop().unwrap() { PrintFrame::Broken(indent, breaks) => { self.indent = indent; breaks } PrintFrame::Fits(breaks) => breaks, }; if cfg!(prettyplease_debug) { self.out.push(match breaks { Breaks::Consistent => '»', Breaks::Inconsistent => '›', }); } } fn print_break(&mut self, token: BreakToken, size: isize) { let fits = token.never_break || match self.get_top() { PrintFrame::Fits(..) => true, PrintFrame::Broken(.., Breaks::Consistent) => false, PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, }; if fits { self.pending_indentation += token.blank_space; self.space -= token.blank_space as isize; if let Some(no_break) = token.no_break { self.out.push(no_break); self.space -= no_break.len_utf8() as isize; } if cfg!(prettyplease_debug) { self.out.push('·'); } } else { if let Some(pre_break) = token.pre_break { self.print_indent(); self.out.push(pre_break); } if cfg!(prettyplease_debug) { self.out.push('·'); } self.out.push('\n'); let indent = self.indent as isize + token.offset; self.pending_indentation = usize::try_from(indent).unwrap(); self.space = cmp::max(MARGIN - indent, MIN_SPACE); if let Some(post_break) = token.post_break { self.print_indent(); self.out.push(post_break); self.space -= post_break.len_utf8() as isize; } } } fn print_string(&mut self, string: Cow<'static, str>) { self.print_indent(); self.out.push_str(&string); self.space -= string.len() as isize; } fn print_indent(&mut self) { self.out.reserve(self.pending_indentation); self.out .extend(iter::repeat(' ').take(self.pending_indentation)); self.pending_indentation = 0; } }