| 1 | // Adapted from https://github.com/rust-lang/rust/blob/1.57.0/compiler/rustc_ast_pretty/src/pp.rs. |
| 2 | // See "Algorithm notes" in the crate-level rustdoc. |
| 3 | |
| 4 | use crate::ring::RingBuffer; |
| 5 | use crate::{MARGIN, MIN_SPACE}; |
| 6 | use std::borrow::Cow; |
| 7 | use std::cmp; |
| 8 | use std::collections::VecDeque; |
| 9 | use std::iter; |
| 10 | |
| 11 | #[derive (Clone, Copy, PartialEq)] |
| 12 | pub enum Breaks { |
| 13 | Consistent, |
| 14 | Inconsistent, |
| 15 | } |
| 16 | |
| 17 | #[derive (Clone, Copy, Default)] |
| 18 | pub struct BreakToken { |
| 19 | pub offset: isize, |
| 20 | pub blank_space: usize, |
| 21 | pub pre_break: Option<char>, |
| 22 | pub post_break: &'static str, |
| 23 | pub no_break: Option<char>, |
| 24 | pub if_nonempty: bool, |
| 25 | pub never_break: bool, |
| 26 | } |
| 27 | |
| 28 | #[derive (Clone, Copy)] |
| 29 | pub struct BeginToken { |
| 30 | pub offset: isize, |
| 31 | pub breaks: Breaks, |
| 32 | } |
| 33 | |
| 34 | #[derive (Clone)] |
| 35 | pub enum Token { |
| 36 | String(Cow<'static, str>), |
| 37 | Break(BreakToken), |
| 38 | Begin(BeginToken), |
| 39 | End, |
| 40 | } |
| 41 | |
| 42 | #[derive (Copy, Clone)] |
| 43 | enum PrintFrame { |
| 44 | Fits(Breaks), |
| 45 | Broken(usize, Breaks), |
| 46 | } |
| 47 | |
| 48 | pub const SIZE_INFINITY: isize = 0xffff; |
| 49 | |
| 50 | pub struct Printer { |
| 51 | out: String, |
| 52 | // Number of spaces left on line |
| 53 | space: isize, |
| 54 | // Ring-buffer of tokens and calculated sizes |
| 55 | buf: RingBuffer<BufEntry>, |
| 56 | // Total size of tokens already printed |
| 57 | left_total: isize, |
| 58 | // Total size of tokens enqueued, including printed and not yet printed |
| 59 | right_total: isize, |
| 60 | // Holds the ring-buffer index of the Begin that started the current block, |
| 61 | // possibly with the most recent Break after that Begin (if there is any) on |
| 62 | // top of it. Values are pushed and popped on the back of the queue using it |
| 63 | // like stack, and elsewhere old values are popped from the front of the |
| 64 | // queue as they become irrelevant due to the primary ring-buffer advancing. |
| 65 | scan_stack: VecDeque<usize>, |
| 66 | // Stack of blocks-in-progress being flushed by print |
| 67 | print_stack: Vec<PrintFrame>, |
| 68 | // Level of indentation of current line |
| 69 | indent: usize, |
| 70 | // Buffered indentation to avoid writing trailing whitespace |
| 71 | pending_indentation: usize, |
| 72 | } |
| 73 | |
| 74 | #[derive (Clone)] |
| 75 | struct BufEntry { |
| 76 | token: Token, |
| 77 | size: isize, |
| 78 | } |
| 79 | |
| 80 | impl Printer { |
| 81 | pub fn new() -> Self { |
| 82 | Printer { |
| 83 | out: String::new(), |
| 84 | space: MARGIN, |
| 85 | buf: RingBuffer::new(), |
| 86 | left_total: 0, |
| 87 | right_total: 0, |
| 88 | scan_stack: VecDeque::new(), |
| 89 | print_stack: Vec::new(), |
| 90 | indent: 0, |
| 91 | pending_indentation: 0, |
| 92 | } |
| 93 | } |
| 94 | |
| 95 | pub fn eof(mut self) -> String { |
| 96 | if !self.scan_stack.is_empty() { |
| 97 | self.check_stack(0); |
| 98 | self.advance_left(); |
| 99 | } |
| 100 | self.out |
| 101 | } |
| 102 | |
| 103 | pub fn scan_begin(&mut self, token: BeginToken) { |
| 104 | if self.scan_stack.is_empty() { |
| 105 | self.left_total = 1; |
| 106 | self.right_total = 1; |
| 107 | self.buf.clear(); |
| 108 | } |
| 109 | let right = self.buf.push(BufEntry { |
| 110 | token: Token::Begin(token), |
| 111 | size: -self.right_total, |
| 112 | }); |
| 113 | self.scan_stack.push_back(right); |
| 114 | } |
| 115 | |
| 116 | pub fn scan_end(&mut self) { |
| 117 | if self.scan_stack.is_empty() { |
| 118 | self.print_end(); |
| 119 | } else { |
| 120 | if !self.buf.is_empty() { |
| 121 | if let Token::Break(break_token) = self.buf.last().token { |
| 122 | if self.buf.len() >= 2 { |
| 123 | if let Token::Begin(_) = self.buf.second_last().token { |
| 124 | self.buf.pop_last(); |
| 125 | self.buf.pop_last(); |
| 126 | self.scan_stack.pop_back(); |
| 127 | self.scan_stack.pop_back(); |
| 128 | self.right_total -= break_token.blank_space as isize; |
| 129 | return; |
| 130 | } |
| 131 | } |
| 132 | if break_token.if_nonempty { |
| 133 | self.buf.pop_last(); |
| 134 | self.scan_stack.pop_back(); |
| 135 | self.right_total -= break_token.blank_space as isize; |
| 136 | } |
| 137 | } |
| 138 | } |
| 139 | let right = self.buf.push(BufEntry { |
| 140 | token: Token::End, |
| 141 | size: -1, |
| 142 | }); |
| 143 | self.scan_stack.push_back(right); |
| 144 | } |
| 145 | } |
| 146 | |
| 147 | pub fn scan_break(&mut self, token: BreakToken) { |
| 148 | if self.scan_stack.is_empty() { |
| 149 | self.left_total = 1; |
| 150 | self.right_total = 1; |
| 151 | self.buf.clear(); |
| 152 | } else { |
| 153 | self.check_stack(0); |
| 154 | } |
| 155 | let right = self.buf.push(BufEntry { |
| 156 | token: Token::Break(token), |
| 157 | size: -self.right_total, |
| 158 | }); |
| 159 | self.scan_stack.push_back(right); |
| 160 | self.right_total += token.blank_space as isize; |
| 161 | } |
| 162 | |
| 163 | pub fn scan_string(&mut self, string: Cow<'static, str>) { |
| 164 | if self.scan_stack.is_empty() { |
| 165 | self.print_string(string); |
| 166 | } else { |
| 167 | let len = string.len() as isize; |
| 168 | self.buf.push(BufEntry { |
| 169 | token: Token::String(string), |
| 170 | size: len, |
| 171 | }); |
| 172 | self.right_total += len; |
| 173 | self.check_stream(); |
| 174 | } |
| 175 | } |
| 176 | |
| 177 | #[track_caller ] |
| 178 | pub fn offset(&mut self, offset: isize) { |
| 179 | match &mut self.buf.last_mut().token { |
| 180 | Token::Break(token) => token.offset += offset, |
| 181 | Token::Begin(_) => {} |
| 182 | Token::String(_) | Token::End => unreachable!(), |
| 183 | } |
| 184 | } |
| 185 | |
| 186 | pub fn end_with_max_width(&mut self, max: isize) { |
| 187 | let mut depth = 1; |
| 188 | for &index in self.scan_stack.iter().rev() { |
| 189 | let entry = &self.buf[index]; |
| 190 | match entry.token { |
| 191 | Token::Begin(_) => { |
| 192 | depth -= 1; |
| 193 | if depth == 0 { |
| 194 | if entry.size < 0 { |
| 195 | let actual_width = entry.size + self.right_total; |
| 196 | if actual_width > max { |
| 197 | self.buf.push(BufEntry { |
| 198 | token: Token::String(Cow::Borrowed("" )), |
| 199 | size: SIZE_INFINITY, |
| 200 | }); |
| 201 | self.right_total += SIZE_INFINITY; |
| 202 | } |
| 203 | } |
| 204 | break; |
| 205 | } |
| 206 | } |
| 207 | Token::End => depth += 1, |
| 208 | Token::Break(_) => {} |
| 209 | Token::String(_) => unreachable!(), |
| 210 | } |
| 211 | } |
| 212 | self.scan_end(); |
| 213 | } |
| 214 | |
| 215 | pub fn ends_with(&self, ch: char) -> bool { |
| 216 | for i in self.buf.index_range().rev() { |
| 217 | if let Token::String(token) = &self.buf[i].token { |
| 218 | return token.ends_with(ch); |
| 219 | } |
| 220 | } |
| 221 | self.out.ends_with(ch) |
| 222 | } |
| 223 | |
| 224 | fn check_stream(&mut self) { |
| 225 | while self.right_total - self.left_total > self.space { |
| 226 | if *self.scan_stack.front().unwrap() == self.buf.index_range().start { |
| 227 | self.scan_stack.pop_front().unwrap(); |
| 228 | self.buf.first_mut().size = SIZE_INFINITY; |
| 229 | } |
| 230 | |
| 231 | self.advance_left(); |
| 232 | |
| 233 | if self.buf.is_empty() { |
| 234 | break; |
| 235 | } |
| 236 | } |
| 237 | } |
| 238 | |
| 239 | fn advance_left(&mut self) { |
| 240 | while self.buf.first().size >= 0 { |
| 241 | let left = self.buf.pop_first(); |
| 242 | |
| 243 | match left.token { |
| 244 | Token::String(string) => { |
| 245 | self.left_total += left.size; |
| 246 | self.print_string(string); |
| 247 | } |
| 248 | Token::Break(token) => { |
| 249 | self.left_total += token.blank_space as isize; |
| 250 | self.print_break(token, left.size); |
| 251 | } |
| 252 | Token::Begin(token) => self.print_begin(token, left.size), |
| 253 | Token::End => self.print_end(), |
| 254 | } |
| 255 | |
| 256 | if self.buf.is_empty() { |
| 257 | break; |
| 258 | } |
| 259 | } |
| 260 | } |
| 261 | |
| 262 | fn check_stack(&mut self, mut depth: usize) { |
| 263 | while let Some(&index) = self.scan_stack.back() { |
| 264 | let entry = &mut self.buf[index]; |
| 265 | match entry.token { |
| 266 | Token::Begin(_) => { |
| 267 | if depth == 0 { |
| 268 | break; |
| 269 | } |
| 270 | self.scan_stack.pop_back().unwrap(); |
| 271 | entry.size += self.right_total; |
| 272 | depth -= 1; |
| 273 | } |
| 274 | Token::End => { |
| 275 | self.scan_stack.pop_back().unwrap(); |
| 276 | entry.size = 1; |
| 277 | depth += 1; |
| 278 | } |
| 279 | Token::Break(_) => { |
| 280 | self.scan_stack.pop_back().unwrap(); |
| 281 | entry.size += self.right_total; |
| 282 | if depth == 0 { |
| 283 | break; |
| 284 | } |
| 285 | } |
| 286 | Token::String(_) => unreachable!(), |
| 287 | } |
| 288 | } |
| 289 | } |
| 290 | |
| 291 | fn get_top(&self) -> PrintFrame { |
| 292 | const OUTER: PrintFrame = PrintFrame::Broken(0, Breaks::Inconsistent); |
| 293 | self.print_stack.last().map_or(OUTER, PrintFrame::clone) |
| 294 | } |
| 295 | |
| 296 | fn print_begin(&mut self, token: BeginToken, size: isize) { |
| 297 | if cfg!(prettyplease_debug) { |
| 298 | self.out.push(match token.breaks { |
| 299 | Breaks::Consistent => '«' , |
| 300 | Breaks::Inconsistent => '‹' , |
| 301 | }); |
| 302 | if cfg!(prettyplease_debug_indent) { |
| 303 | self.out |
| 304 | .extend(token.offset.to_string().chars().map(|ch| match ch { |
| 305 | '0' ..='9' => ['₀' , '₁' , '₂' , '₃' , '₄' , '₅' , '₆' , '₇' , '₈' , '₉' ] |
| 306 | [(ch as u8 - b'0' ) as usize], |
| 307 | '-' => '₋' , |
| 308 | _ => unreachable!(), |
| 309 | })); |
| 310 | } |
| 311 | } |
| 312 | if size > self.space { |
| 313 | self.print_stack |
| 314 | .push(PrintFrame::Broken(self.indent, token.breaks)); |
| 315 | self.indent = usize::try_from(self.indent as isize + token.offset).unwrap(); |
| 316 | } else { |
| 317 | self.print_stack.push(PrintFrame::Fits(token.breaks)); |
| 318 | } |
| 319 | } |
| 320 | |
| 321 | fn print_end(&mut self) { |
| 322 | let breaks = match self.print_stack.pop().unwrap() { |
| 323 | PrintFrame::Broken(indent, breaks) => { |
| 324 | self.indent = indent; |
| 325 | breaks |
| 326 | } |
| 327 | PrintFrame::Fits(breaks) => breaks, |
| 328 | }; |
| 329 | if cfg!(prettyplease_debug) { |
| 330 | self.out.push(match breaks { |
| 331 | Breaks::Consistent => '»' , |
| 332 | Breaks::Inconsistent => '›' , |
| 333 | }); |
| 334 | } |
| 335 | } |
| 336 | |
| 337 | fn print_break(&mut self, token: BreakToken, size: isize) { |
| 338 | let fits = token.never_break |
| 339 | || match self.get_top() { |
| 340 | PrintFrame::Fits(..) => true, |
| 341 | PrintFrame::Broken(.., Breaks::Consistent) => false, |
| 342 | PrintFrame::Broken(.., Breaks::Inconsistent) => size <= self.space, |
| 343 | }; |
| 344 | if fits { |
| 345 | self.pending_indentation += token.blank_space; |
| 346 | self.space -= token.blank_space as isize; |
| 347 | if let Some(no_break) = token.no_break { |
| 348 | self.out.push(no_break); |
| 349 | self.space -= no_break.len_utf8() as isize; |
| 350 | } |
| 351 | if cfg!(prettyplease_debug) { |
| 352 | self.out.push('·' ); |
| 353 | } |
| 354 | } else { |
| 355 | if let Some(pre_break) = token.pre_break { |
| 356 | self.print_indent(); |
| 357 | self.out.push(pre_break); |
| 358 | } |
| 359 | if cfg!(prettyplease_debug) { |
| 360 | self.out.push('·' ); |
| 361 | } |
| 362 | self.out.push(' \n' ); |
| 363 | let indent = self.indent as isize + token.offset; |
| 364 | self.pending_indentation = usize::try_from(indent).unwrap(); |
| 365 | self.space = cmp::max(MARGIN - indent, MIN_SPACE); |
| 366 | if !token.post_break.is_empty() { |
| 367 | self.print_indent(); |
| 368 | self.out.push_str(token.post_break); |
| 369 | self.space -= token.post_break.len() as isize; |
| 370 | } |
| 371 | } |
| 372 | } |
| 373 | |
| 374 | fn print_string(&mut self, string: Cow<'static, str>) { |
| 375 | self.print_indent(); |
| 376 | self.out.push_str(&string); |
| 377 | self.space -= string.len() as isize; |
| 378 | } |
| 379 | |
| 380 | fn print_indent(&mut self) { |
| 381 | self.out.reserve(self.pending_indentation); |
| 382 | self.out |
| 383 | .extend(iter::repeat(' ' ).take(self.pending_indentation)); |
| 384 | self.pending_indentation = 0; |
| 385 | } |
| 386 | } |
| 387 | |