blob: b6dea57c3ce040143d8707a28dd545cec6148a1d [file] [log] [blame]
// 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<char>,
pub post_break: Option<char>,
pub no_break: Option<char>,
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<BufEntry>,
// 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<usize>,
// Stack of blocks-in-progress being flushed by print
print_stack: Vec<PrintFrame>,
// 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 mut 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;
}
}