Fork of daniellemaywood.uk/gleam — Wasm codegen work
1

Configure Feed

Select the types of activity you want to include in your feed.

gleam / pretty-arena / src / lib.rs
64 kB 1545 lines
1// SPDX-License-Identifier: Apache-2.0 2// SPDX-FileCopyrightText: 2026 The Gleam contributors 3 4//! This module implements the functionality described in 5//! ["Strictly Pretty" (2000) by Christian Lindig][0], with a few 6//! extensions. 7//! 8//! This module is heavily influenced by Elixir's Inspect.Algebra and 9//! JavaScript's Prettier. 10//! 11//! [0]: http://citeseerx.ist.psu.edu/viewdoc/summary?doi=10.1.1.34.2200 12//! 13//! ## Extensions 14//! 15//! - `ForcedBreak` from Elixir. 16//! - `FlexBreak` from Elixir. 17//! 18//! The way this module works is fairly simple conceptually, however the actual 19//! behaviour in practice can be hard to wrap one's head around. 20//! 21//! The basic premise is the `Document` type, which is a tree structure, 22//! containing some text as well as information on how it can be formatted. 23//! Once the document is constructed, it can be printed using the 24//! `to_pretty_string` function. 25//! 26//! It will then traverse the tree, and construct 27//! a string, attempting to wrap lines to that they do not exceed the line length 28//! limit specified. Where and when it wraps lines is determined by the structure 29//! of the `Document` itself. 30//! 31#![allow(clippy::wrong_self_convention)] 32 33use std::{cell::RefCell, rc::Rc}; 34 35use ecow::{EcoString, eco_format}; 36use num_bigint::BigInt; 37use typed_arena::Arena; 38use unicode_segmentation::UnicodeSegmentation; 39 40/// Join multiple documents together in a vector. This macro calls the `to_doc` 41/// method on each element, providing a concise way to write a document sequence. 42/// For example: 43/// 44/// ```rust:norun 45/// docvec!["Hello", line(), "world!"] 46/// ``` 47/// 48/// Note: each document in a docvec is not separated in any way: the formatter 49/// will never break a line unless a `Document::Break` or `Document::Line` 50/// is used. Therefore, `docvec!["a", "b", "c"]` is equivalent to 51/// `"abc".to_doc()`. 52/// 53#[macro_export] 54macro_rules! docvec { 55 // When we're joining exactly 3 or 4 documents we use the specialised 56 // `join3` and `join4` to be a bit faster! 57 ($arena:expr, $first:expr, $second:expr, $third:expr) => { 58 $arena.join3($first, $second, $third) 59 }; 60 61 ($arena:expr, $first:expr, $second:expr, $third:expr, $fourth:expr) => { 62 $arena.join4($first, $second, $third, $fourth) 63 }; 64 65 // Otherwise we just append all the documents together. 66 ($arena:expr, $first:expr, $($rest:expr),+ $(,)?) => { 67 { 68 $first.to_doc(&$arena) 69 $(.append(&$arena, $rest))+ 70 } 71 }; 72} 73 74/// A trait that allows for objects to observe the cursor position as it is being formatted. 75/// This is useful for any operations that need to track the exact position a document is 76/// being written to in a buffer such as for source mapping. 77pub trait CursorPositionObserver: std::fmt::Debug { 78 fn observe_cursor_position(&mut self, line: isize, width: isize); 79} 80 81// To the outside world a document is an opaque data structure. 82// This is a newtype wrapper around a reference that is gonna be stored in a 83// document arena. 84#[derive(Debug, Clone, Copy)] 85pub struct Document<'string, 'doc>(&'doc PrintableDocument<'string, 'doc>); 86 87/// Coerce a value into a Document. 88/// Note we do not implement this for String as a slight pressure to favour str 89/// over String. 90pub trait Documentable<'string, 'doc> { 91 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc>; 92} 93 94impl<'string, 'doc> Documentable<'string, 'doc> for char { 95 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 96 eco_format!("{self}").to_doc(arena) 97 } 98} 99 100impl<'string, 'doc> Documentable<'string, 'doc> for &'string str { 101 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 102 Document(arena.documents.alloc(PrintableDocument::Str { 103 graphemes: self.graphemes(true).count() as isize, 104 string: self, 105 })) 106 } 107} 108 109impl<'string, 'doc> Documentable<'string, 'doc> for EcoString { 110 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 111 Document(arena.documents.alloc(PrintableDocument::EcoString { 112 graphemes: self.graphemes(true).count() as isize, 113 string: self, 114 })) 115 } 116} 117 118impl<'string, 'doc> Documentable<'string, 'doc> for &EcoString { 119 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 120 self.clone().to_doc(arena) 121 } 122} 123 124impl<'string, 'doc> Documentable<'string, 'doc> for isize { 125 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 126 eco_format!("{self}").to_doc(arena) 127 } 128} 129 130impl<'string, 'doc> Documentable<'string, 'doc> for i64 { 131 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 132 eco_format!("{self}").to_doc(arena) 133 } 134} 135 136impl<'string, 'doc> Documentable<'string, 'doc> for usize { 137 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 138 eco_format!("{self}").to_doc(arena) 139 } 140} 141 142impl<'string, 'doc> Documentable<'string, 'doc> for f64 { 143 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 144 eco_format!("{self:?}").to_doc(arena) 145 } 146} 147 148impl<'string, 'doc> Documentable<'string, 'doc> for u64 { 149 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 150 eco_format!("{self:?}").to_doc(arena) 151 } 152} 153 154impl<'string, 'doc> Documentable<'string, 'doc> for u32 { 155 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 156 eco_format!("{self}").to_doc(arena) 157 } 158} 159 160impl<'string, 'doc> Documentable<'string, 'doc> for u16 { 161 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 162 eco_format!("{self}").to_doc(arena) 163 } 164} 165 166impl<'string, 'doc> Documentable<'string, 'doc> for u8 { 167 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 168 eco_format!("{self}").to_doc(arena) 169 } 170} 171 172impl<'string, 'doc> Documentable<'string, 'doc> for BigInt { 173 fn to_doc(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 174 eco_format!("{self}").to_doc(arena) 175 } 176} 177 178impl<'string, 'doc> Documentable<'string, 'doc> for Document<'string, 'doc> { 179 fn to_doc(self, _arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 180 self 181 } 182} 183 184impl<'string, 'doc> Document<'string, 'doc> { 185 /// Groups a document. When pretty printing a group, the formatter will 186 /// first attempt to fit the entire group on one line. If it fails, all 187 /// `break_` documents in the group will render broken. 188 /// 189 /// Nested groups are handled separately to their parents, so if the 190 /// outermost group is broken, any sub-groups might be rendered broken 191 /// or unbroken, depending on whether they fit on a single line. 192 pub fn group(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 193 match self.0 { 194 // Grouping a group doesn't change how it will be formatted so we 195 // can avoid boxing it. 196 PrintableDocument::Group(_) 197 // Same for an empty document. 198 | PrintableDocument::Empty 199 // Grouping a literal string will never change how it's formatted, 200 // we can avoid boxing it. 201 | PrintableDocument::Str { .. } 202 | PrintableDocument::EcoString { .. } 203 | PrintableDocument::ZeroWidthString { .. } 204 | PrintableDocument::ZeroWidthStr { .. } 205 | PrintableDocument::CursorPositionObserver { .. } => self, 206 207 PrintableDocument::Line(_) 208 | PrintableDocument::ForceBroken(_) 209 | PrintableDocument::NextBreakFits(..) 210 | PrintableDocument::Break { .. } 211 | PrintableDocument::Join(..) 212 | PrintableDocument::Join3(..) 213 | PrintableDocument::Join4(..) 214 | PrintableDocument::Nest(..) => Document(arena.documents.alloc(PrintableDocument::Group(self))), 215 } 216 } 217 218 /// Sets the indentation level of a document. 219 pub fn set_nesting( 220 self, 221 arena: &'doc DocumentArena<'string, 'doc>, 222 indent: isize, 223 ) -> Document<'string, 'doc> { 224 Document(arena.documents.alloc(PrintableDocument::Nest( 225 indent, 226 NestMode::Set, 227 NestCondition::Always, 228 self, 229 ))) 230 } 231 232 /// Nests a document by a certain indentation. When rending linebreaks, the 233 /// formatter will print a new line followed by the current indentation. 234 pub fn nest( 235 self, 236 arena: &'doc DocumentArena<'string, 'doc>, 237 indent: isize, 238 ) -> Document<'string, 'doc> { 239 Document(arena.documents.alloc(PrintableDocument::Nest( 240 indent, 241 NestMode::Increase, 242 NestCondition::Always, 243 self, 244 ))) 245 } 246 247 /// Nests a document by a certain indentation, but only if the current 248 /// group is broken. 249 pub fn nest_if_broken( 250 self, 251 arena: &'doc DocumentArena<'string, 'doc>, 252 indent: isize, 253 ) -> Document<'string, 'doc> { 254 Document(arena.documents.alloc(PrintableDocument::Nest( 255 indent, 256 NestMode::Increase, 257 NestCondition::IfBroken, 258 self, 259 ))) 260 } 261 262 /// Forces all `break_` and `flex_break` documents in the current group 263 /// to render broken. 264 pub fn force_break(self, arena: &'doc DocumentArena<'string, 'doc>) -> Document<'string, 'doc> { 265 Document(arena.documents.alloc(PrintableDocument::ForceBroken(self))) 266 } 267 268 /// Force the next `Break` to render unbroken, regardless of whether it 269 /// fits on the line or not. 270 pub fn next_break_fits( 271 self, 272 arena: &'doc DocumentArena<'string, 'doc>, 273 mode: NextBreakFitsMode, 274 ) -> Document<'string, 'doc> { 275 Document( 276 arena 277 .documents 278 .alloc(PrintableDocument::NextBreakFits(self, mode)), 279 ) 280 } 281 282 /// Appends one document to another. Equivalent to `docvec![self, second]`, 283 /// except that it `self` is already a `Document::Vec`, it will append 284 /// directly to it instead of allocating a new vector. 285 /// 286 /// Useful when chaining multiple documents together in a fashion where 287 /// they cannot be put all into one `docvec!` macro. 288 pub fn append( 289 self, 290 arena: &'doc DocumentArena<'string, 'doc>, 291 new: impl Documentable<'string, 'doc>, 292 ) -> Document<'string, 'doc> { 293 let new = new.to_doc(arena); 294 if let PrintableDocument::Empty = new.0 { 295 return self; 296 } 297 298 match self.0 { 299 PrintableDocument::Empty => new, 300 PrintableDocument::Join(first, second) => Document( 301 arena 302 .documents 303 .alloc(PrintableDocument::Join3(*first, *second, new)), 304 ), 305 PrintableDocument::Join3(first, second, third) => Document( 306 arena 307 .documents 308 .alloc(PrintableDocument::Join4(*first, *second, *third, new)), 309 ), 310 PrintableDocument::Line(..) 311 | PrintableDocument::Join4(..) 312 | PrintableDocument::ForceBroken(..) 313 | PrintableDocument::NextBreakFits(..) 314 | PrintableDocument::Break { .. } 315 | PrintableDocument::Nest(..) 316 | PrintableDocument::Group(..) 317 | PrintableDocument::Str { .. } 318 | PrintableDocument::EcoString { .. } 319 | PrintableDocument::ZeroWidthString { .. } 320 | PrintableDocument::ZeroWidthStr { .. } 321 | PrintableDocument::CursorPositionObserver { .. } => { 322 Document(arena.documents.alloc(PrintableDocument::Join(self, new))) 323 } 324 } 325 } 326 327 /// Surrounds a document in two delimiters. Equivalent to 328 /// `docvec![option, self, closed]`. 329 pub fn surround( 330 self, 331 arena: &'doc DocumentArena<'string, 'doc>, 332 open: impl Documentable<'string, 'doc>, 333 closed: impl Documentable<'string, 'doc>, 334 ) -> Document<'string, 'doc> { 335 open.to_doc(arena) 336 .append(arena, self) 337 .append(arena, closed.to_doc(arena)) 338 } 339 340 /// Returns true when the document contains no printable characters 341 /// (whitespace and newlines are considered printable characters). 342 pub fn is_empty(&self) -> bool { 343 match self.0 { 344 PrintableDocument::Empty => true, 345 PrintableDocument::Line(n) => *n == 0, 346 PrintableDocument::EcoString { string, .. } => string.is_empty(), 347 PrintableDocument::Str { string, .. } => string.is_empty(), 348 // assuming `broken` and `unbroken` are equivalent 349 PrintableDocument::Break { broken, .. } => broken.is_empty(), 350 PrintableDocument::ForceBroken(document) 351 | PrintableDocument::Nest(_, _, _, document) 352 | PrintableDocument::Group(document) 353 | PrintableDocument::NextBreakFits(document, _) => document.is_empty(), 354 PrintableDocument::Join(first, second) => first.is_empty() && second.is_empty(), 355 PrintableDocument::Join3(first, second, third) => { 356 first.is_empty() && second.is_empty() && third.is_empty() 357 } 358 PrintableDocument::Join4(first, second, third, fourth) => { 359 first.is_empty() && second.is_empty() && third.is_empty() && fourth.is_empty() 360 } 361 // Zero-width strings don't count towards line length, but they are 362 // still printed and so are not empty. (Unless their string contents 363 // is also empty) 364 PrintableDocument::ZeroWidthString { string } => string.is_empty(), 365 PrintableDocument::ZeroWidthStr { string } => string.is_empty(), 366 PrintableDocument::CursorPositionObserver { .. } => true, 367 } 368 } 369 370 /// Prints a document into a `String`, attempting to limit lines to `limit` 371 /// characters in length. 372 pub fn to_pretty_string(self, limit: isize) -> String { 373 let mut buffer = String::new(); 374 self.pretty_print(limit, &mut buffer) 375 .expect("Writing to string buffer failed"); 376 buffer 377 } 378 379 /// Prints a document into `writer`, attempting to limit lines to `limit` 380 /// characters in length. 381 pub fn pretty_print(self, limit: isize, writer: &mut impl std::fmt::Write) -> std::fmt::Result { 382 let docs = im::vector![(0, Mode::Unbroken, self)]; 383 format(writer, limit, docs)?; 384 Ok(()) 385 } 386} 387 388/// A pretty printable document. A tree structure, made up of text and other 389/// elements which determine how it can be formatted. 390/// 391/// The variants of this enum should probably not be constructed directly, 392/// rather use the helper functions on the DocumentArena to construct them. 393/// 394#[derive(Debug, Clone)] 395enum PrintableDocument<'string, 'doc> { 396 /// A mandatory linebreak. This is always printed as a string of newlines, 397 /// equal in length to the number specified. 398 Line(usize), 399 400 /// Forces the breaks of the wrapped document to be considered as not 401 /// fitting on a single line. Used in combination with a `Group` it can be 402 /// used to force its `Break`s to always break. 403 ForceBroken(Document<'string, 'doc>), 404 405 /// Ignore the next break, forcing it to render as unbroken. 406 NextBreakFits(Document<'string, 'doc>, NextBreakFitsMode), 407 408 /// A document after which the formatter can insert a newline. This determines 409 /// where line breaks can occur, outside of hardcoded `Line`s. 410 /// See `break_` and `flex_break` for usage. 411 Break { 412 broken: &'string str, 413 unbroken: &'string str, 414 kind: BreakKind, 415 }, 416 417 /// Join multiple documents together. The documents are not separated in any 418 /// way: the formatter will only print newlines if `Document::Break` or 419 /// `Document::Line` is used. 420 // maybe experiment with Slice(&'doc [Document<'string, 'doc>]), 421 Join(Document<'string, 'doc>, Document<'string, 'doc>), 422 Join3( 423 Document<'string, 'doc>, 424 Document<'string, 'doc>, 425 Document<'string, 'doc>, 426 ), 427 Join4( 428 Document<'string, 'doc>, 429 Document<'string, 'doc>, 430 Document<'string, 'doc>, 431 Document<'string, 'doc>, 432 ), 433 434 /// Nests the given document by the given indent, depending on the specified 435 /// condition. See `Document::nest`, `Document::set_nesting` and 436 /// `Document::nest_if_broken` for usages. 437 Nest(isize, NestMode, NestCondition, Document<'string, 'doc>), 438 439 /// Groups a document. When pretty printing a group, the formatter will 440 /// first attempt to fit the entire group on one line. If it fails, all 441 /// `break_` documents in the group will render broken. 442 /// 443 /// Nested groups are handled separately to their parents, so if the 444 /// outermost group is broken, any sub-groups might be rendered broken 445 /// or unbroken, depending on whether they fit on a single line. 446 Group(Document<'string, 'doc>), 447 448 /// Renders a string slice. This will always render the string verbatim, 449 /// without any line breaks or other modifications to it. 450 Str { 451 string: &'string str, 452 /// The number of extended grapheme clusters in the string. 453 /// This is what the pretty printer uses as the width of the string as it 454 /// is closes to what a human would consider the "length" of a string. 455 /// 456 /// Since computing the number of grapheme clusters requires walking over 457 /// the string we precompute it to avoid iterating through a string over 458 /// and over again in the pretty printing algorithm. 459 /// 460 graphemes: isize, 461 }, 462 463 /// Renders an `EcoString`. This will always render the string verbatim, 464 /// without any line breaks or other modifications to it. 465 EcoString { 466 string: EcoString, 467 /// The number of extended grapheme clusters in the string. 468 /// This is what the pretty printer uses as the width of the string as it 469 /// is closes to what a human would consider the "length" of a string. 470 /// 471 /// Since computing the number of grapheme clusters requires walking over 472 /// the string we precompute it to avoid iterating through a string over 473 /// and over again in the pretty printing algorithm. 474 /// 475 graphemes: isize, 476 }, 477 478 /// A string that is not taken into account when determining line length. 479 /// This is useful for additional formatting text which won't be rendered 480 /// in the final output, such as ANSI codes or HTML elements. 481 ZeroWidthString { 482 string: EcoString, 483 }, 484 ZeroWidthStr { 485 string: &'string str, 486 }, 487 488 /// A node that gets notified of the cursor position as it is being formatted. 489 /// This allows for processes outside of the final output to be notified of 490 /// the cursor position and perform actions based on it, such as recording 491 /// the span of the node in the generated source code for a source mapping. 492 CursorPositionObserver { 493 observer: Rc<RefCell<dyn CursorPositionObserver>>, 494 }, 495 Empty, 496} 497 498/// The kind of line break this `Document::Break` is. 499#[derive(Debug, Clone, Copy, PartialEq, Eq)] 500pub enum BreakKind { 501 /// A `flex_break`. 502 Flex, 503 /// A `break_`. 504 Strict, 505} 506 507#[derive(Debug, Clone, Copy, PartialEq, Eq)] 508enum Mode { 509 /// The mode used when a group doesn't fit on a single line: when `Broken` 510 /// the `Break`s inside it will be rendered as newlines, splitting the 511 /// group. 512 Broken, 513 514 /// The default mode used when a group can fit on a single line: all its 515 /// `Break`s will be rendered as their unbroken string and kept on a single 516 /// line. 517 Unbroken, 518 519 /// This mode is used by the `NextBreakFit` document to force a break to be 520 /// considered as broken. 521 ForcedBroken, 522 523 /// This mode is used to disable a `NextBreakFit` document. 524 ForcedUnbroken, 525} 526 527/// A flag that can be used to enable or disable a `NextBreakFit` document. 528#[derive(Debug, Clone, Copy, PartialEq, Eq)] 529pub enum NextBreakFitsMode { 530 Enabled, 531 Disabled, 532} 533 534/// A flag that can be used to conditionally disable a `Nest` document. 535#[derive(Debug, Clone, Copy, PartialEq, Eq)] 536pub enum NestCondition { 537 /// This always applies the nesting. This is a sensible default that will 538 /// work for most of the cases. 539 Always, 540 /// Only applies the nesting if the wrapping `Group` couldn't fit on a 541 /// single line and has been broken. 542 IfBroken, 543} 544 545/// Used to change the way nesting of documents work. 546#[derive(Debug, Clone, Copy, PartialEq, Eq)] 547pub enum NestMode { 548 /// If the nesting mode is `Increase`, the current indentation will be 549 /// increased by the specified value. 550 Increase, 551 /// If the nesting mode is `Set`, the current indentation is going to be set 552 /// to exactly the specified value. 553 /// 554 /// `doc.nest(2).set_nesting(0)` 555 /// "wibble 556 /// wobble <- no indentation is added! 557 /// wubble" 558 Set, 559} 560 561macro_rules! const_str { 562 ($name:ident, $string:expr, $graphemes:expr) => { 563 pub const $name: $crate::Document<'static, 'static> = { 564 $crate::Document(&$crate::PrintableDocument::Str { 565 string: $string, 566 graphemes: $graphemes, 567 }) 568 }; 569 }; 570} 571 572macro_rules! const_zero_width_str { 573 ($name:ident, $string:expr) => { 574 pub const $name: $crate::Document<'static, 'static> = 575 { $crate::Document(&$crate::PrintableDocument::ZeroWidthStr { string: $string }) }; 576 }; 577} 578 579macro_rules! const_break { 580 ($name:ident, $broken:expr, $unbroken:expr) => { 581 pub const $name: $crate::Document<'static, 'static> = { 582 $crate::Document(&$crate::PrintableDocument::Break { 583 broken: $broken, 584 unbroken: $unbroken, 585 kind: $crate::BreakKind::Strict, 586 }) 587 }; 588 }; 589} 590 591/// The empty document that renders as nothing. 592pub const EMPTY_DOCUMENT: Document<'static, 'static> = Document(&PrintableDocument::Empty); 593 594pub const LINE_DOCUMENT: Document<'static, 'static> = Document(&PrintableDocument::Line(1)); 595 596pub const TWO_LINES_DOCUMENT: Document<'static, 'static> = Document(&PrintableDocument::Line(2)); 597 598pub const FLEX_COMMA_DOCUMENT: Document<'static, 'static> = Document(&PrintableDocument::Break { 599 broken: ",", 600 unbroken: ", ", 601 kind: BreakKind::Flex, 602}); 603 604const_str!(SPACE_DOCUMENT, " ", 1); 605const_str!(MODULE_COMMENT_DOCUMENT, "////", 4); 606const_str!(DOC_COMMENT_DOCUMENT, "///", 3); 607const_str!(CONST_SPACE_DOCUMENT, "const ", 5); 608const_str!(OPAQUE_TYPE_SPACE_DOCUMENT, "opaque type ", 12); 609const_str!(TYPE_SPACE_DOCUMENT, "type ", 5); 610const_str!(IMPORT_SPACE_DOCUMENT, "import ", 7); 611const_str!(SPACE_AS_SPACE_DOCUMENT, " as ", 4); 612const_str!(SPACE_EQUAL_SPACE_DOCUMENT, " = ", 3); 613const_str!(TODO_DOCUMENT, "todo", 4); 614const_str!(CONCAT_DOCUMENT, "<>", 2); 615const_str!(DOT_DOCUMENT, ".", 1); 616const_str!(DOT_DOT_DOCUMENT, "..", 2); 617const_str!(COLON_SPACE_DOCUMENT, ": ", 2); 618const_str!(OPEN_CURLY_DOCUMENT, "{", 1); 619const_str!(CLOSE_CURLY_DOCUMENT, "}", 1); 620const_str!(OPEN_SQUARE_DOCUMENT, "[", 1); 621const_str!(CLOSE_SQUARE_DOCUMENT, "]", 1); 622const_str!(OPEN_CLOSE_SQUARE_DOCUMENT, "[]", 2); 623const_str!(COMMENT_DOCUMENT, "//", 2); 624const_str!(ECHO_SPACE_DOCUMENT, "echo ", 5); 625const_str!(AS_DOCUMENT, "as", 2); 626const_str!(SPACE_AS_DOCUMENT, " as", 2); 627const_str!(OPEN_PAREN_DOCUMENT, "(", 1); 628const_str!(CLOSE_PAREN_DOCUMENT, ")", 1); 629const_str!(OPEN_CLOSE_PAREN_DOCUMENT, "()", 2); 630const_str!(USE_DOCUMENT, "use", 3); 631const_str!(USE_AND_ARROW_DOCUMENT, "use <-", 6); 632const_str!(LEFT_ARROW_DOCUMENT, "<-", 2); 633const_str!(MINUS_SPACE_DOCUMENT, "- ", 2); 634const_str!(EXCLAMATION_MARK_DOCUMENT, "!", 1); 635const_str!(ASSERT_SPACE_DOCUMENT, "assert ", 7); 636const_str!(PIPE_SPACE_DOCUMENT, "|> ", 3); 637const_str!(COLON_DOCUMENT, ":", 1); 638const_str!(HASHTAG_DOCUMENT, "#", 1); 639const_str!(SPACE_OPEN_CURLY_DOCUMENT, " {", 2); 640const_str!(EMPTY_TUPLE_DOCUMENT, "#()", 3); 641const_str!(OPEN_TUPLE_DOCUMENT, "#(", 2); 642const_str!(FN_DOCUMENT, "fn", 2); 643const_str!(FN_SPACE_DOCUMENT, "fn ", 3); 644const_str!(PANIC_DOCUMENT, "panic", 5); 645const_str!(IF_SPACE_DOCUMENT, "if ", 2); 646const_str!(SPACE_RIGHT_ARROW_DOCUMENT, " ->", 3); 647const_str!(SPACE_RIGHT_ARROW_SPACE_DOCUMENT, " -> ", 4); 648const_str!(LET_SPACE_DOCUMENT, "let ", 4); 649const_str!(LET_ASSERT_SPACE_DOCUMENT, "let assert ", 11); 650const_str!(SPACE_EQUAL_DOCUMENT, " =", 2); 651const_str!(UNDERSCORE_DOCUMENT, "_", 1); 652const_str!(DOUBLE_QUOTE_DOCUMENT, "\"", 1); 653const_str!(OPEN_PAREN_DOT_DOT_CLOSE_PAREN_DOCUMENT, "(..)", 4); 654const_str!(VERTICAL_BAR_SPACE_DOCUMENT, "| ", 2); 655const_str!(SPACE_CONCAT_SPACE_DOCUMENT, " <> ", 4); 656const_str!(SPACE_PLUS_SPACE_DOCUMENT, " + ", 3); 657const_str!(SPACE_MINUS_SPACE_DOCUMENT, " - ", 3); 658const_str!(SPACE_TIMES_SPACE_DOCUMENT, " * ", 3); 659const_str!(SPACE_MODULE_SPACE_DOCUMENT, " % ", 3); 660const_str!(SPACE_SLASH_SPACE_DOCUMENT, " / ", 3); 661const_str!(SPACE_CLOSE_CURLY_DOCUMENT, " }", 2); 662const_str!(OPEN_CURLY_SPACE_DOCUMENT, "{ ", 2); 663const_str!(CLOSE_BIT_ARRAY_DOCUMENT, ">>", 2); 664const_str!(ECHO_DOCUMENT, "echo", 4); 665const_str!(EMPTY_BIT_ARRAY_DOCUMENT, "<<>>", 4); 666const_str!(OPEN_BIT_ARRAY_DOCUMENT, "<<", 2); 667const_str!(RIGHT_ARROW_DOCUMENT, "->", 2); 668const_str!(PUB_SPACE_DOCUMENT, "pub ", 4); 669const_str!(SIZE_DOCUMENT, "size", 4); 670const_str!(BYTES_DOCUMENT, "bytes", 5); 671const_str!(BITS_DOCUMENT, "bits", 4); 672const_str!(INT_DOCUMENT, "int", 3); 673const_str!(FLOAT_DOCUMENT, "float", 5); 674const_str!(UTF8_DOCUMENT, "utf8", 4); 675const_str!(UTF16_DOCUMENT, "utf16", 5); 676const_str!(UTF32_DOCUMENT, "utf32", 5); 677const_str!(UTF8_CODEPOINT_DOCUMENT, "utf8_codepoint", 14); 678const_str!(UTF16_CODEPOINT_DOCUMENT, "utf16_codepoint", 15); 679const_str!(UTF32_CODEPOINT_DOCUMENT, "utf32_codepoint", 15); 680const_str!(SIGNED_DOCUMENT, "signed", 6); 681const_str!(UNSIGNED_DOCUMENT, "unsigned", 8); 682const_str!(BIG_DOCUMENT, "big", 3); 683const_str!(LITTLE_DOCUMENT, "little", 6); 684const_str!(NATIVE_DOCUMENT, "native", 6); 685const_str!(UNIT_DOCUMENT, "unit", 4); 686const_str!(AND_DOCUMENT, "&&", 2); 687const_str!(OR_DOCUMENT, "||", 2); 688const_str!(LT_INT_DOCUMENT, "<", 1); 689const_str!(LT_EQ_INT_DOCUMENT, "<=", 2); 690const_str!(LT_FLOAT_DOCUMENT, "<.", 2); 691const_str!(LT_EQ_FLOAT_DOCUMENT, "<=.", 3); 692const_str!(EQ_DOCUMENT, "==", 2); 693const_str!(NOT_EQ_DOCUMENT, "!=", 2); 694const_str!(GT_EQ_INT_DOCUMENT, ">=", 2); 695const_str!(GT_INT_DOCUMENT, ">", 1); 696const_str!(GT_EQ_FLOAT_DOCUMENT, ">=.", 3); 697const_str!(GT_FLOAT_DOCUMENT, ">.", 2); 698const_str!(ADD_INT_DOCUMENT, "+", 1); 699const_str!(ADD_FLOAT_DOCUMENT, "+.", 2); 700const_str!(SUB_INT_DOCUMENT, "-", 1); 701const_str!(SUB_FLOAT_DOCUMENT, "-.", 2); 702const_str!(MULT_INT_DOCUMENT, "*", 1); 703const_str!(MULT_FLOAT_DOCUMENT, "*.", 2); 704const_str!(DIV_INT_DOCUMENT, "/", 1); 705const_str!(DIV_FLOAT_DOCUMENT, "/.", 2); 706const_str!(REMAINDER_INT_DOCUMENT, "%", 1); 707const_str!(DEPRECATED_ATTRIBUTE_QUOTE_DOCUMENT, "@deprecated(\"", 13); 708const_str!(QUOTE_CLOSE_PAREN_DOCUMENT, "\")", 2); 709const_str!(EXTERNAL_ERLANG_QUOTE_DOCUMENT, "@external(erlang, \"", 19); 710const_str!( 711 EXTERNAL_JAVASCRIPT_QUOTE_DOCUMENT, 712 "@external(javascript, \"", 713 23 714); 715const_str!( 716 EXTERNAL_WASM_QUOTE_DOCUMENT, 717 "@external(wasm, \"", 718 17 719); 720const_str!(QUOTE_COMMA_SPACE_QUOTE_DOCUMENT, "\", \"", 4); 721const_str!(INTERNAL_ATTRIBUTE_DOCUMENT, "@internal", 9); 722const_str!(INTERNAL_ATTRIBUTE_SPACE_DOCUMENT, "@internal ", 9); 723const_str!(TRUE_LOWERCASE_DOCUMENT, "true", 4); 724const_str!(FALSE_LOWERCASE_DOCUMENT, "false", 5); 725const_str!(OPEN_CLOSE_CURLY_DOCUMENT, "{}", 2); 726const_str!(SPACE_TIMES_AT_IGNORE_DOCUMENT, " * @ignore", 10); 727const_str!(SLASH_TIMES_TIMES_DOCUMENT, "/**", 3); 728const_str!(SPACE_TIMES_SLASH_DOCUMENT, " */", 3); 729const_str!(CONST_FILEPATH_EQUALS_DOCUMENT, "const FILEPATH = ", 17); 730const_str!(SEMICOLON_DOCUMENT, ";", 1); 731const_str!(FUNCTION_SPACE_DOCUMENT, "function ", 9); 732const_str!(EXPORT_FUNCTION_SPACE_DOCUMENT, "export function ", 16); 733const_str!(EXPORT_CONST_SPACE_DOCUMENT, "export const ", 13); 734const_str!(SUPER_CALL_SEMICOLON_DOCUMENT, "super();", 8); 735const_str!(CONSTRUCTOR_OPEN_PAREN_DOCUMENT, "constructor(", 12); 736const_str!(CLOSE_PAREN_SPACE_OPEN_CURLY_DOCUMENT, ") {", 3); 737const_str!(THIS_DOT_DOCUMENT, "this.", 5); 738const_str!(THIS_OPEN_SQUARE_DOCUMENT, "this[", 5); 739const_str!(CLOSE_SQUARE_SPACE_EQUAL_SPACE_DOCUMENT, "] = ", 4); 740const_str!( 741 SPACE_EXTENDS_CUSTOM_TYPE_OPEN_CURLY_DOCUMENT, 742 " extends $CustomType {", 743 22 744); 745const_str!(CLASS_SPACE_DOCUMENT, "class ", 6); 746const_str!(EXPORT_CLASS_SPACE_DOCUMENT, "export class ", 13); 747const_str!(VALUE_DOT_DOCUMENT, "value.", 6); 748const_str!(SPACE_EQUAL_SPACE_VALUE_SPACE_ARROW, " = (value) =>", 13); 749const_str!( 750 SPACE_EQUAL_OPEN_CLOSE_PAREN_SPACE_ARROW_DOCUMENT, 751 " = () =>", 752 8 753); 754const_str!(CLOSE_PAREN_SEMICOLON_DOCUMENT, ");", 2); 755const_str!(NEW_SPACE_DOCUMENT, "new ", 4); 756const_str!(VALUE_OPEN_SQUARE_DOCUMENT, "value[", 6); 757const_str!(CLOSE_SQUARE_SEMICOLON_DOCUMENT, "];", 2); 758const_str!(DOLLAR_IS_DOCUMENT, "$is", 3); 759const_str!(VALUE_INSTANCE_OF_SPACE_DOCUMENT, "value instanceof ", 17); 760const_str!(DOLLAR_DOCUMENT, "$", 1); 761const_str!(CLOSE_PAREN_ARROW_DOCUMENT, ") =>", 4); 762const_str!(CLOSE_PAREN_SLIM_ARROW_DOCUMENT, ") ->", 4); 763const_str!(SPACE_EQUAL_SPACE_OPEN_PAREN_DOCUMENT, " = (", 4); 764const_str!(DOLLAR_CONST_SEMICOLON_DOCUMENT, "$const;", 7); 765const_str!(DOLLAR_CONST_DOCUMENT, "$const", 6); 766const_str!(OPEN_CLOSE_PAREN_SEMICOLON_DOCUMENT, "();", 3); 767const_str!(EXPORT_SPACE_OPEN_CLOSE_CURLY_DOCUMENT, "export {}", 9); 768const_str!(EXPORT_SPACE_OPEN_CURLY_DOCUMENT, "export {", 8); 769const_str!(EXPORT_TYPE_SPACE_OPEN_CURLY_DOCUMENT, "export type {", 13); 770const_str!(DOT_MJS_DOT_MAP_DOCUMENT, ".mjs.map", 8); 771const_str!( 772 DOT_D_DOT_MTS_CLOSE_QUOTE_CLOSE_TAG_DOCUMENT, 773 ".d.mts\" />", 774 10 775); 776const_str!( 777 SOURCE_MAPPING_URL_EQUAL_DOCUMENT, 778 "//# sourceMappingURL=", 779 21 780); 781const_str!(REFERENCE_TYPES_DOCUMENT, "/// <reference types=\"./", 24); 782const_str!(CLOSE_CURLY_SEMICOLON_DOCUMENT, "};", 2); 783const_str!(TIMES_SPACE_AS_SPACE_DOCUMENT, "* as ", 5); 784const_str!(SPACE_FROM_SPACE_DOUBLE_QUOTE_DOCUMENT, " from \"", 7); 785const_str!(DOUBLE_QUOTE_SEMICOLON_DOCUMENT, "\";", 2); 786const_str!( 787 CLOSE_CURLY_SPACE_FROM_SPACE_DOUBLE_QUOTE_DOCUMENT, 788 "} from \"", 789 8 790); 791const_str!(EXPORT_TYPE_SPACE_DOCUMENT, "export type ", 12); 792const_str!(X_DOCUMENT, "x", 1); 793const_str!(ANY_DOCUMENT, "any", 3); 794const_str!(EXPORT_SPACE_DOCUMENT, "export ", 7); 795const_str!(DECLARE_SPACE_DOCUMENT, "declare ", 8); 796const_str!( 797 SPACE_EXTENDS_UNDERSCORE_CUSTOM_TYPE_DOCUMENT, 798 " extends _.CustomType {", 799 23 800); 801const_str!(CONSTRUCTOR_DOCUMENT, "constructor", 11); 802const_str!( 803 DEPRECATED_MULTILINE_COMMENT_DOCUMENT, 804 "/** @deprecated */", 805 18 806); 807const_str!(CLOSE_PAREN_COLON_SPACE_DOCUMENT, "): ", 3); 808const_str!(VALUE_COLON_SPACE_ANY_DOCUMENT, "value: any", 10); 809const_str!( 810 CLOSE_PAREN_COLON_SPACE_VALUE_IS_SPACE_DOCUMENT, 811 "): value is ", 812 12 813); 814const_str!(LT_INT_UNKNOWN_DOCUMENT, "<unknown", 8); 815const_str!(COMMA_SPACE_UNKNOWN_DOCUMENT, ", unknown", 9); 816const_str!(VALUE_COLON_SPACE_DOCUMENT, "value: ", 7); 817const_str!(UNDEFINED_DOCUMENT, "undefined", 9); 818const_str!(NUMBER_DOCUMENT, "number", 6); 819const_str!(UNDERSCORE_DOT_UTF_CODEPOINT_DOCUMENT, "_.UtfCodepoint", 14); 820const_str!(STRING_DOCUMENT, "string", 6); 821const_str!(BOOLEAN_DOCUMENT, "boolean", 7); 822const_str!(UNDERSCORE_DOT_BIT_ARRAY_DOCUMENT, "_.BitArray", 10); 823const_str!(UNDERSCORE_DOT_LIST_DOCUMENT, "_.List", 6); 824const_str!(UNDERSCORE_DOT_RESULT_DOCUMENT, "_.Result", 8); 825const_str!(SPACE_EQUAL_RIGHT_ARROW_SPACE_DOCUMENT, " => ", 4); 826const_str!(IF_SPACE_OPEN_PAREN_DOCUMENT, "if (", 4); 827const_str!(CLOSE_PAREN_SPACE_DOCUMENT, ") ", 2); 828const_str!(DOUBLE_CLOSE_PAREN_SPACE_DOCUMENT, ")) ", 3); 829const_str!( 830 IF_SPACE_OPEN_PAREN_EXCLAMATION_OPEN_PAREN_DOCUMENT, 831 "if (!(", 832 6 833); 834const_str!(SPACE_ELSE_SPACE_DOCUMENT, " else ", 6); 835const_str!(DOT_CHAR_CODE_AT_ZERO_DOCUMENT, ".charCodeAt(0)", 14); 836const_str!(SPACE_TRIPLE_EQUAL_SPACE_DOCUMENT, " === ", 5); 837const_str!(TRIPLE_EQUAL_DOCUMENT, "===", 3); 838const_str!(DOT_STARTS_WITH_OPEN_PAREN_DOCUMENT, ".startsWith(", 12); 839const_str!(ZERO_DOCUMENT, "0", 1); 840const_str!(DOT_BIT_SIZE_MODULO_8_DOCUMENT, ".bitSize % 8", 12); 841const_str!(DOT_BIT_SIZE_MINUS_SPACE_DOCUMENT, ".bitSize - ", 11); 842const_str!(DOT_BIT_SIZE_DOCUMENT, ".bitSize", 8); 843const_str!(CLOSE_PAREN_MODULO_8_DOCUMENT, ") % 8", 5); 844const_str!(SPACE_GT_EQ_ZERO_DOCUMENT, " >= 0", 5); 845const_str!(SPACE_GT_EQ_SPACE_DOCUMENT, " >= ", 4); 846const_str!( 847 GLOBAL_THIS_DOT_NUMBER_DOT_IS_FINITE_OPEN_PAREN_DOCUMENT, 848 "globalThis.Number.isFinite(", 849 27 850); 851const_str!(SPACE_INSTANCE_OF_SPACE_DOCUMENT, " instanceof ", 12); 852const_str!( 853 SPACE_INSTANCE_OF_NON_EMPTY_DOCUMENT, 854 " instanceof $NonEmpty", 855 21 856); 857const_str!(SPACE_INSTANCE_OF_EMPTY_DOCUMENT, " instanceof $Empty", 18); 858const_str!(DOT_BYTE_AT_OPEN_PAREN_DOCUMENT, ".byteAt(", 8); 859const_str!(COMMA_SPACE_DOCUMENT, ", ", 2); 860const_str!( 861 BIT_ARRAY_SLICE_TO_INT_OPEN_PAREN_DOCUMENT, 862 "bitArraySliceToInt(", 863 19 864); 865const_str!( 866 BIT_ARRAY_SLICE_TO_FLOAT_OPEN_PAREN_DOCUMENT, 867 "bitArraySliceToFloat(", 868 21 869); 870const_str!(BIT_ARRAY_SLICE_OPEN_PAREN_DOCUMENT, "bitArraySlice(", 14); 871const_str!(RETURN_SPACE_DOCUMENT, "return ", 7); 872const_str!(SPACE_EQUAL_LOOP_DOLLAR_DOCUMENT, " = loop$", 8); 873const_str!(WHILE_TRUE_OPEN_PAREN_DOCUMENT, "while (true) {", 14); 874const_str!( 875 DOLLAR_LIST_DOLLAR_EMPTY_DOLLAR_CONST_DOCUMENT, 876 "$List$Empty$const", 877 17 878); 879const_str!(SIZED_INT_OPEN_PAREN_DOCUMENT, "sizedInt(", 9); 880const_str!(SIZED_FLOAT_OPEN_PAREN_DOCUMENT, "sizedFloat(", 11); 881const_str!(STRING_BITS_OPEN_PAREN_DOCUMENT, "stringBits(", 11); 882const_str!(STRING_TO_UTF16_OPEN_PAREN_DOCUMENT, "stringToUtf16(", 14); 883const_str!(STRING_TO_UTF32_OPEN_PAREN_DOCUMENT, "stringToUtf32(", 14); 884const_str!( 885 CODEPOINT_TO_UTF16_OPEN_PAREN_DOCUMENT, 886 "codepointToUtf16(", 887 17 888); 889const_str!( 890 CODEPOINT_TO_UTF32_OPEN_PAREN_DOCUMENT, 891 "codepointToUtf32(", 892 17 893); 894const_str!(TO_BIT_ARRAY_OPEN_PAREN_DOCUMENT, "toBitArray(", 11); 895const_str!(COMMA_ZERO_COMMA_SPACE, ", 0, ", 5); 896const_str!(THROW_MAKE_ERROR_DOCUMENT, "throw makeError", 15); 897const_str!(FILEPATH_UPPERCASE_DOCUMENT, "FILEPATH", 8); 898const_str!(SPACE_EQUAL_ARROW_SPACE_OPEN_CURLY_DOCUMENT, " => {", 5); 899const_str!(INFINITY_DOCUMENT, "Infinity", 8); 900const_str!(MINUS_INFINITY_DOCUMENT, "-Infinity", 9); 901const_str!(NAN_DOCUMENT, "NaN", 3); 902const_str!(TO_LIST_OPEN_PAREN_DOCUMENT, "toList(", 7); 903const_str!(LIST_PREPEND_DOCUMENT, "listPrepend", 11); 904const_str!(CLOSE_CURLY_CLOSE_PAREN_OPEN_CLOSE_PAREN_DOCUMENT, "})()", 4); 905const_str!( 906 OPEN_PAREN_OPEN_CLOSE_PAREN_EQUAL_ARROW_OPEN_CURLY_DOCUMENT, 907 "(() => {", 908 8 909); 910const_str!(CODEPOINT_BITS_OPEN_PAREN_DOCUMENT, "codepointBits(", 14); 911const_str!(CLOSE_CURLY_ELSE_OPEN_CURLY_DOCUMENT, "} else {", 8); 912const_str!(EXCLAMATION_MARK_OPEN_PAREN_DOCUMENT, "!(", 2); 913const_str!(SPACE_DOUBLE_VERTICAL_BAR_SPACE_DOCUMENT, " || ", 4); 914const_str!(SPACE_DOUBLE_AMPERSAND_SPACE_DOCUMENT, " && ", 4); 915const_str!(LOOP_DOLLAR_DOCUMENT, "loop$", 5); 916const_str!(DIVIDE_INT_DOCUMENT, "divideInt", 9); 917const_str!(DIVIDE_FLOAT_DOCUMENT, "divideFloat", 9); 918const_str!( 919 GLOBAL_THIS_DOT_MATH_DOT_TRUNC_DOCUMENT, 920 "globalThis.Math.trunc", 921 21 922); 923const_str!(SPACE_MODULO_SPACE_DOCUMENT, " % ", 3); 924const_str!( 925 SPACE_EXCLAMATION_MARK_DOUBLE_EQUAL_SPACE_DOCUMENT, 926 " !== ", 927 5 928); 929const_str!(EXCLAMATION_MARK_DOUBLE_EQUAL_DOCUMENT, "!==", 3); 930const_str!(IS_EQUAL_DOCUMENT, "isEqual", 7); 931const_str!(EXCLAMATION_MARK_IS_EQUAL_DOCUMENT, "!isEqual", 8); 932const_str!(SPACE_LT_INT_SPACE_DOCUMENT, " < ", 3); 933const_str!(SPACE_LT_EQ_INT_SPACE_DOCUMENT, " <= ", 4); 934const_str!(SPACE_GT_INT_SPACE_DOCUMENT, " > ", 3); 935const_str!(SPACE_GT_EQ_INT_SPACE_DOCUMENT, " >= ", 4); 936const_str!(CAMEL_CASE_REMAINDER_INT_DOCUMENT, "remainderInt", 12); 937const_str!(PURE_JAVASCRIPT_COMMENT_DOCUMENT, "/* @__PURE__ */ ", 16); 938const_str!(LOWERCASE_ARG_DOCUMENT, "arg", 3); 939const_str!(PUB_TYPE_SPACE_DOCUMENT, "pub type ", 9); 940const_str!(PUB_FN_SPACE_DOCUMENT, "pub fn ", 7); 941const_str!(PUB_CONST_SPACE_DOCUMENT, "pub const ", 10); 942const_str!(PUB_OPAQUE_TYPE_SPACE_DOCUMENT, "pub opaque type ", 16); 943const_str!(FN_OPEN_PAREN_DOCUMENT, "fn(", 3); 944 945const_zero_width_str!(ZERO_WIDTH_CLOSED_SPAN_TAG_DOCUMENT, "</span>"); 946const_zero_width_str!(ZERO_WIDTH_CLOSED_A_TAG_DOCUMENT, "</a>"); 947const_zero_width_str!( 948 ZERO_WIDTH_OPEN_VARIABLE_COLOUR_SPAN, 949 r#"<span class="hljs-variable">"# 950); 951const_zero_width_str!( 952 ZERO_WIDTH_OPEN_TITLE_COLOUR_SPAN, 953 r#"<span class="hljs-title">"# 954); 955const_zero_width_str!( 956 ZERO_WIDTH_OPEN_KEYWORD_COLOUR_SPAN, 957 r#"<span class="hljs-keyword">"# 958); 959const_zero_width_str!( 960 ZERO_WIDTH_OPEN_COMMENT_COLOUR_SPAN, 961 r#"<span class="hljs-comment">"# 962); 963 964const_break!(EMPTY_BREAK_DOCUMENT, "", ""); 965const_break!(BREAKABLE_SPACE_DOCUMENT, "", " "); 966const_break!(COMMA_BREAK_DOCUMENT, ",", ", "); 967const_break!(TRAILING_COMMA_BREAK_DOCUMENT, ",", ""); 968const_break!(TRAILING_COMMA_OR_SPACE_BREAK_DOCUMENT, ",", " "); 969const_break!(OPEN_CURLY_BREAK_DOCUMENT, "{", "{ "); 970const_break!(OPEN_SQUARE_BREAK_DOCUMENT, "[", "["); 971const_break!(OPEN_PAREN_BREAK_DOCUMENT, "(", "("); 972const_break!(OPEN_TUPLE_BREAK_DOCUMENT, "#(", "#("); 973const_break!(OPEN_BIT_ARRAY_BREAK_DOCUMENT, "<<", "<<"); 974const_break!(CASE_BREAK_DOCUMENT, "case", "case "); 975const_break!(SPACE_DOUBLE_AMPERSAND_BREAK_DOCUMENT, " &&", " && "); 976 977/// A structure used to efficiently allocate documents that can then be pretty 978/// printed. 979pub struct DocumentArena<'string, 'doc> { 980 documents: Arena<PrintableDocument<'string, 'doc>>, 981} 982 983impl<'string, 'doc> std::fmt::Debug for DocumentArena<'string, 'doc> { 984 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { 985 f.debug_struct("DocumentArena").finish() 986 } 987} 988 989impl<'string, 'doc> Default for DocumentArena<'string, 'doc> { 990 fn default() -> Self { 991 Self::new() 992 } 993} 994 995impl<'string, 'doc> DocumentArena<'string, 'doc> { 996 /// Creates a new empty arena with no documents allocated. 997 pub fn new() -> Self { 998 let documents = Arena::new(); 999 Self { documents } 1000 } 1001 1002 /// Allocates a document that is rendered as a single line. 1003 pub fn line(&'doc self) -> Document<'string, 'doc> { 1004 self.lines(1) 1005 } 1006 1007 pub fn position_observer( 1008 &'doc self, 1009 observer: Rc<RefCell<dyn CursorPositionObserver>>, 1010 ) -> Document<'string, 'doc> { 1011 Document( 1012 self.documents 1013 .alloc(PrintableDocument::CursorPositionObserver { observer }), 1014 ) 1015 } 1016 1017 /// Renders a string of newlines, equal in length to the number provided. 1018 #[inline] 1019 pub fn lines(&'doc self, i: usize) -> Document<'string, 'doc> { 1020 Document(self.documents.alloc(PrintableDocument::Line(i))) 1021 } 1022 1023 /// A document after which the formatter can insert a newline. This determines 1024 /// where line breaks can occur, outside of hardcoded `Line`s. 1025 /// 1026 /// If the formatter determines that a group cannot fit on a single line, 1027 /// all breaks in the group will be rendered as broken. Otherwise, they 1028 /// will be rendered as unbroken. 1029 /// 1030 /// A broken `Break` renders the `broken` string, followed by a newline. 1031 /// An unbroken `Break` renders the `unbroken` string by itself. 1032 /// 1033 /// For example: 1034 /// ```rust:norun 1035 /// let document = docvec!["Hello", break_("", ", "), "world!"]; 1036 /// assert_eq!(document.to_pretty_string(20), "Hello, world!"); 1037 /// assert_eq!(document.to_pretty_string(10), "Hello\nworld!"); 1038 /// ``` 1039 /// 1040 pub fn break_( 1041 &'doc self, 1042 broken: &'string str, 1043 unbroken: &'string str, 1044 ) -> Document<'string, 'doc> { 1045 Document(self.documents.alloc(PrintableDocument::Break { 1046 broken, 1047 unbroken, 1048 kind: BreakKind::Strict, 1049 })) 1050 } 1051 1052 /// A document after which the formatter can insert a newline, similar to 1053 /// `break_()`. The difference is that when a group is rendered broken, all 1054 /// breaks are rendered broken. However, `flex_break` decides whether to 1055 /// break or not for every individual `flex_break`. 1056 /// 1057 /// For example: 1058 /// ```rust:norun 1059 /// let with_breaks = docvec!["Hello", break_("", ", "), "pretty", break_("", ", "), "printed!"]; 1060 /// assert_eq!(with_breaks.to_pretty_string(20), "Hello\npretty\nprinted!"); 1061 /// 1062 /// let with_flex_breaks = docvec!["Hello", flex_break("", ", "), "pretty", flex_break("", ", "), "printed!"]; 1063 /// assert_eq!(with_flex_breaks.to_pretty_string(20), "Hello, pretty\nprinted!"); 1064 /// ``` 1065 /// 1066 pub fn flex_break( 1067 &'doc self, 1068 broken: &'string str, 1069 unbroken: &'string str, 1070 ) -> Document<'string, 'doc> { 1071 Document(self.documents.alloc(PrintableDocument::Break { 1072 broken, 1073 unbroken, 1074 kind: BreakKind::Flex, 1075 })) 1076 } 1077 1078 /// A string that is not taken into account when determining line length. 1079 /// This is useful for additional formatting text which won't be rendered 1080 /// in the final output, such as ANSI codes or HTML elements. 1081 /// 1082 /// For example: 1083 /// ```rust:norun 1084 /// let document = docvec!["Hello", zero_width_string("This is a very long string"), break_("", ""), "world"]; 1085 /// assert_eq!(document.to_pretty_string(20), "HelloThis is a very long stringworld"); 1086 /// ``` 1087 /// 1088 pub fn zero_width_string(&'doc self, string: EcoString) -> Document<'string, 'doc> { 1089 Document( 1090 self.documents 1091 .alloc(PrintableDocument::ZeroWidthString { string }), 1092 ) 1093 } 1094 1095 /// Same as the `zero_width_string` but this works with string references. 1096 /// This is useful when you already have a string reference and you want to 1097 /// avoid allocating a further string. 1098 /// 1099 pub fn zero_width_str(&'doc self, string: &'string str) -> Document<'string, 'doc> { 1100 Document( 1101 self.documents 1102 .alloc(PrintableDocument::ZeroWidthStr { string }), 1103 ) 1104 } 1105 1106 /// Joins together an iterator of documents into a single document. 1107 /// All the documents are gonna be rendered next to each other with no 1108 /// spaces in between. 1109 pub fn concat( 1110 &'doc self, 1111 documents: impl IntoIterator<Item = Document<'string, 'doc>>, 1112 ) -> Document<'string, 'doc> { 1113 let mut documents = documents.into_iter().peekable(); 1114 let Some(mut previous) = documents.next() else { 1115 return EMPTY_DOCUMENT; 1116 }; 1117 1118 while let Some(second) = documents.next() { 1119 previous = if let Some(third) = documents.next() { 1120 if let Some(fourth) = documents.next() { 1121 docvec![self, previous, second, third, fourth] 1122 } else { 1123 docvec![self, previous, second, third] 1124 } 1125 } else { 1126 previous.append(self, second) 1127 }; 1128 } 1129 1130 previous 1131 } 1132 1133 /// Joins an iterator into a single document, interspersing each element with 1134 /// another document. This is useful for example in argument lists, where a 1135 /// list of arguments must all be separated with a comma. 1136 pub fn join( 1137 &'doc self, 1138 elements: impl IntoIterator<Item = Document<'string, 'doc>>, 1139 separator: Document<'string, 'doc>, 1140 ) -> Document<'string, 'doc> { 1141 let mut elements = elements.into_iter().peekable(); 1142 let Some(mut previous) = elements.next() else { 1143 return EMPTY_DOCUMENT; 1144 }; 1145 1146 for next in elements { 1147 previous = docvec![self, previous, separator, next]; 1148 } 1149 1150 previous 1151 } 1152 1153 /// Joins exactly three documents into one. 1154 /// This is a little optimisation over joining three documents using `join` 1155 /// or `.append`. 1156 pub fn join3( 1157 &'doc self, 1158 first: impl Documentable<'string, 'doc>, 1159 second: impl Documentable<'string, 'doc>, 1160 third: impl Documentable<'string, 'doc>, 1161 ) -> Document<'string, 'doc> { 1162 Document(self.documents.alloc(PrintableDocument::Join3( 1163 first.to_doc(self), 1164 second.to_doc(self), 1165 third.to_doc(self), 1166 ))) 1167 } 1168 1169 /// Joins exactly four documents into one. 1170 /// This is a little optimisation over joining three documents using `join` 1171 /// or `.append`. 1172 pub fn join4( 1173 &'doc self, 1174 first: impl Documentable<'string, 'doc>, 1175 second: impl Documentable<'string, 'doc>, 1176 third: impl Documentable<'string, 'doc>, 1177 fourth: impl Documentable<'string, 'doc>, 1178 ) -> Document<'string, 'doc> { 1179 Document(self.documents.alloc(PrintableDocument::Join4( 1180 first.to_doc(self), 1181 second.to_doc(self), 1182 third.to_doc(self), 1183 fourth.to_doc(self), 1184 ))) 1185 } 1186} 1187 1188fn format<'string, 'doc>( 1189 writer: &mut impl std::fmt::Write, 1190 limit: isize, 1191 mut docs: im::Vector<(isize, Mode, Document<'string, 'doc>)>, 1192) -> std::fmt::Result { 1193 let mut line: isize = 0; 1194 let mut width: isize = 0; 1195 // As long as there are documents to print we'll take each one by one and 1196 // output the corresponding string to the given writer. 1197 // 1198 // Each document in the `docs` queue also has an accompanying indentation 1199 // and mode: 1200 // - the indentation is used to keep track of the current indentation, 1201 // you might notice in [ref:format-nest] that it adds documents to the 1202 // queue increasing their current indentation. 1203 // - the mode is used to keep track of the state of the documents inside a 1204 // group. For example, if a group doesn't fit on a single line its 1205 // documents will be split into multiple lines and the mode set to 1206 // `Broken` to keep track of this. 1207 while let Some((indent, mode, document)) = docs.pop_front() { 1208 match document.0 { 1209 // When we run into a line we print the given number of newlines and 1210 // add the indentation required by the given document. 1211 PrintableDocument::Line(count) => { 1212 for _ in 0..*count { 1213 writer.write_str("\n")?; 1214 } 1215 line += *count as isize; 1216 for _ in 0..indent { 1217 writer.write_char(' ')?; 1218 } 1219 width = indent; 1220 } 1221 1222 // Flex breaks are NOT conditional to the mode: if the mode is 1223 // already `Unbroken`, then the break is left unbroken (like strict 1224 // breaks); any other mode is ignored. 1225 // A flexible break will only be split if the following documents 1226 // can't fit on the same line; otherwise, it is just displayed as an 1227 // unbroken `Break`. 1228 PrintableDocument::Break { 1229 broken, 1230 unbroken, 1231 kind: BreakKind::Flex, 1232 } => { 1233 let unbroken_width = width + unbroken.len() as isize; 1234 // Every time we need to check again if the remaining piece can 1235 // fit. If it does, the flexible break is not broken. 1236 if mode == Mode::Unbroken || fits(limit, unbroken_width, docs.clone()) { 1237 writer.write_str(unbroken)?; 1238 width = unbroken_width; 1239 } else { 1240 writer.write_str(broken)?; 1241 writer.write_char('\n')?; 1242 line += 1; 1243 for _ in 0..indent { 1244 writer.write_char(' ')?; 1245 } 1246 width = indent; 1247 } 1248 } 1249 1250 // Strict breaks are conditional to the mode. They differ from 1251 // flexible break because, if a group gets split - that is the mode 1252 // is `Broken` or `ForceBroken` - ALL of the breaks in that group 1253 // will be split. You can notice the difference with flexible breaks 1254 // because here we only check the mode and then take action; before 1255 // we would try and see if the remaining documents fit on a single 1256 // line before deciding if the (flexible) break can be split or not. 1257 PrintableDocument::Break { 1258 broken, 1259 unbroken, 1260 kind: BreakKind::Strict, 1261 } => match mode { 1262 // If the mode requires the break to be broken, then its broken 1263 // string is printed, then we start a newline and indent it 1264 // according to the current indentation level. 1265 Mode::Broken | Mode::ForcedBroken => { 1266 writer.write_str(broken)?; 1267 writer.write_char('\n')?; 1268 line += 1; 1269 for _ in 0..indent { 1270 writer.write_char(' ')?; 1271 } 1272 width = indent; 1273 } 1274 // If the mode doesn't require the break to be broken, then its 1275 // unbroken string is printed as if it were a normal string; 1276 // also updating the width of the current line. 1277 Mode::Unbroken | Mode::ForcedUnbroken => { 1278 writer.write_str(unbroken)?; 1279 width += unbroken.len() as isize 1280 } 1281 }, 1282 1283 // Strings are printed as they are and the current width is 1284 // increased accordingly. 1285 PrintableDocument::EcoString { string, graphemes } => { 1286 width += graphemes; 1287 writer.write_str(string)?; 1288 } 1289 1290 PrintableDocument::Str { string, graphemes } => { 1291 width += graphemes; 1292 writer.write_str(string)?; 1293 } 1294 1295 PrintableDocument::ZeroWidthString { string } => { 1296 // We write the string, but do not increment the length 1297 writer.write_str(string)?; 1298 } 1299 PrintableDocument::ZeroWidthStr { string } => { 1300 // We write the string, but do not increment the length 1301 writer.write_str(string)?; 1302 } 1303 1304 // If multiple documents need to be printed, then they are all 1305 // pushed to the front of the queue and will be printed one by one. 1306 PrintableDocument::Join(first, second) => { 1307 // Just like `fits`, the elements will be pushed _on the front_ 1308 // of the queue. In order to keep their original order they need 1309 // to be pushed in reverse order. 1310 docs.push_front((indent, mode, *second)); 1311 docs.push_front((indent, mode, *first)); 1312 } 1313 PrintableDocument::Join3(first, second, third) => { 1314 docs.push_front((indent, mode, *third)); 1315 docs.push_front((indent, mode, *second)); 1316 docs.push_front((indent, mode, *first)); 1317 } 1318 PrintableDocument::Join4(first, second, third, fourth) => { 1319 docs.push_front((indent, mode, *fourth)); 1320 docs.push_front((indent, mode, *third)); 1321 docs.push_front((indent, mode, *second)); 1322 docs.push_front((indent, mode, *first)); 1323 } 1324 1325 // A `Nest` document doesn't result in anything being printed, its 1326 // only effect is to increase the current nesting level for the 1327 // wrapped document [tag:format-nest]. 1328 PrintableDocument::Nest(i, nest_mode, condition, doc) => match (condition, mode) { 1329 // The nesting is only applied under two conditions: 1330 // - either the nesting condition is `Always`. 1331 // - or the condition is `IfBroken` and the group was actually 1332 // broken (that is, the current mode is `Broken`). 1333 (NestCondition::Always, _) | (NestCondition::IfBroken, Mode::Broken) => { 1334 let new_indent = match nest_mode { 1335 NestMode::Increase => indent + i, 1336 NestMode::Set => *i, 1337 }; 1338 docs.push_front((new_indent, mode, *doc)) 1339 } 1340 // If none of the above conditions is met, then the nesting is 1341 // not applied. 1342 _ => docs.push_front((indent, mode, *doc)), 1343 }, 1344 1345 PrintableDocument::Group(doc) => { 1346 // When we see a group we first try and see if it can fit on a 1347 // single line without breaking any break; that is why we use 1348 // the `Unbroken` mode here: we want to try to fit everything on 1349 // a single line. 1350 let group_docs = im::vector![(indent, Mode::Unbroken, *doc)]; 1351 if fits(limit, width, group_docs) { 1352 // If everything can stay on a single line we print the 1353 // wrapped document with the `Unbroken` mode, leaving all 1354 // the group's break as unbroken. 1355 docs.push_front((indent, Mode::Unbroken, *doc)); 1356 } else { 1357 // Otherwise, we need to break the group. We print the 1358 // wrapped document changing its mode to `Broken` so that 1359 // all its breaks will be split on newlines. 1360 docs.push_front((indent, Mode::Broken, *doc)); 1361 } 1362 } 1363 1364 // `ForceBroken` and `NextBreakFits` only change the way the `fit` 1365 // function works but do not actually change the formatting of a 1366 // document by themselves. That's why when we run into those we 1367 // just go on printing the wrapped document without altering the 1368 // current mode. 1369 PrintableDocument::ForceBroken(document) 1370 | PrintableDocument::NextBreakFits(document, _) => { 1371 docs.push_front((indent, mode, *document)); 1372 } 1373 1374 PrintableDocument::CursorPositionObserver { observer } => { 1375 // Notify the observer of the current cursor position 1376 observer.borrow_mut().observe_cursor_position(line, width); 1377 } 1378 1379 PrintableDocument::Empty => (), 1380 } 1381 } 1382 Ok(()) 1383} 1384 1385fn fits<'string, 'doc>( 1386 limit: isize, 1387 mut current_width: isize, 1388 mut docs: im::Vector<(isize, Mode, Document<'string, 'doc>)>, 1389) -> bool { 1390 // The `fits` function is going to take each document from the `docs` queue 1391 // and check if those can fit on a single line. In order to do so documents 1392 // are going to be pushed in front of this queue and have to be accompanied 1393 // by additional information: 1394 // - the document indentation, that can be increased by the `Nest` block. 1395 // - the current mode; this is needed to know if a group is being broken or 1396 // not and treat `Break`s differently as a consequence. You can see how 1397 // the behaviour changes in [ref:break-fit]. 1398 // 1399 // The loop might be broken earlier without checking all documents under one 1400 // of two conditions: 1401 // - the documents exceed the line `limit` and surely won't fit 1402 // [ref:document-unfit]. 1403 // - the documents are sure to fit the line - for example, if we meet a 1404 // broken `Break` [ref:break-fit] or a newline [ref:newline-fit]. 1405 loop { 1406 // [tag:document-unfit] If we've exceeded the maximum width allowed for 1407 // a line, it means that the document won't fit on a single line, we can 1408 // break the loop. 1409 if current_width > limit { 1410 return false; 1411 }; 1412 1413 // We start by checking the first document of the queue. If there's no 1414 // documents then we can safely say that it fits (if reached this point 1415 // it means that the limit wasn't exceeded). 1416 let (indent, mode, document) = match docs.pop_front() { 1417 Some(x) => x, 1418 None => return true, 1419 }; 1420 1421 match document.0 { 1422 // If a document is marked as `ForceBroken` we can immediately say 1423 // that it doesn't fit, so that every break is going to be 1424 // forcefully broken. 1425 PrintableDocument::ForceBroken(doc) => match mode { 1426 // If the mode is `ForcedBroken` it means that we have to ignore 1427 // this break [ref:forced-broken], so we go check the inner 1428 // document ignoring the effects of this one. 1429 Mode::ForcedBroken => docs.push_front((indent, mode, *doc)), 1430 Mode::Broken | Mode::Unbroken | Mode::ForcedUnbroken => return false, 1431 }, 1432 1433 // [tag:newline-fit] When we run into a line we know that the 1434 // document has a bit that fits in the current line; if it didn't 1435 // fit (that is, it exceeded the maximum allowed width) the loop 1436 // would have been broken by one of the earlier checks. 1437 PrintableDocument::Line(_) => return true, 1438 1439 // If the nesting level is increased we go on checking the wrapped 1440 // document and increase its indentation level based on the nesting 1441 // condition. 1442 PrintableDocument::Nest(i, nest_mode, condition, doc) => match condition { 1443 NestCondition::IfBroken => docs.push_front((indent, mode, *doc)), 1444 NestCondition::Always => { 1445 let new_indent = match nest_mode { 1446 NestMode::Increase => indent + i, 1447 NestMode::Set => *i, 1448 }; 1449 docs.push_front((new_indent, mode, *doc)) 1450 } 1451 }, 1452 1453 // As a general rule, a group fits if it can stay on a single line 1454 // without its breaks being broken down. 1455 PrintableDocument::Group(doc) => match mode { 1456 // If an outer group was broken, we still try to fit the inner 1457 // group on a single line, that's why for the inner document 1458 // we change the mode back to `Unbroken`. 1459 Mode::Broken => docs.push_front((indent, Mode::Unbroken, *doc)), 1460 // Any other mode is preserved as-is: if the mode is forced it 1461 // has to be left unchanged, and if the mode is already unbroken 1462 // there's no need to change it. 1463 Mode::Unbroken | Mode::ForcedBroken | Mode::ForcedUnbroken => { 1464 docs.push_front((indent, mode, *doc)) 1465 } 1466 }, 1467 1468 // When we run into a string we increase the current_width; looping 1469 // back we will check if we've exceeded the maximum allowed width. 1470 PrintableDocument::Str { graphemes, .. } 1471 | PrintableDocument::EcoString { graphemes, .. } => current_width += graphemes, 1472 1473 // Zero width strings do nothing: they do not contribute to line length 1474 PrintableDocument::ZeroWidthString { .. } 1475 | PrintableDocument::ZeroWidthStr { .. } 1476 | PrintableDocument::CursorPositionObserver { .. } => {} 1477 1478 // If we get to a break we need to first see if it has to be 1479 // rendered as its unbroken or broken string, depending on the mode. 1480 PrintableDocument::Break { unbroken, .. } => match mode { 1481 // [tag:break-fit] If the break has to be broken we're done! 1482 // We haven't exceeded the maximum length (otherwise the loop 1483 // iteration would have stopped with one of the earlier checks), 1484 // and - since it needs to be broken - we'll have to go on a new 1485 // line anyway. 1486 // This means that the document inspected so far will fit on a 1487 // single line, thus we return true. 1488 Mode::Broken | Mode::ForcedBroken => return true, 1489 // If the break is not broken then it will be rendered inline as 1490 // its unbroken string, so we treat it exactly as if it were a 1491 // normal string. 1492 Mode::Unbroken | Mode::ForcedUnbroken => current_width += unbroken.len() as isize, 1493 }, 1494 1495 // The `NextBreakFits` can alter the current mode to `ForcedBroken` 1496 // or `ForcedUnbroken` based on its enabled flag. 1497 PrintableDocument::NextBreakFits(doc, enabled) => match enabled { 1498 // [tag:disable-next-break] If it is disabled then we check the 1499 // wrapped document changing the mode to `ForcedUnbroken`. 1500 NextBreakFitsMode::Disabled => { 1501 docs.push_front((indent, Mode::ForcedUnbroken, *doc)) 1502 } 1503 NextBreakFitsMode::Enabled => match mode { 1504 // If we're in `ForcedUnbroken` mode it means that the check 1505 // was disabled by a document wrapping this one 1506 // [ref:disable-next-break]; that's why we do nothing and 1507 // check the wrapped document as if it were a normal one. 1508 Mode::ForcedUnbroken => docs.push_front((indent, mode, *doc)), 1509 // [tag:forced-broken] Any other mode is turned into 1510 // `ForcedBroken` so that when we run into a break, the 1511 // response to the question "Does the document fit?" will be 1512 // yes [ref:break-fit]. 1513 // This is why this is called `NextBreakFit` I think. 1514 Mode::Broken | Mode::Unbroken | Mode::ForcedBroken => { 1515 docs.push_front((indent, Mode::ForcedBroken, *doc)) 1516 } 1517 }, 1518 }, 1519 1520 // If there's a sequence of documents we will check each one, one 1521 // after the other to see if - as a whole - they can fit on a single 1522 // line. 1523 PrintableDocument::Join(first, second) => { 1524 // The array needs to be reversed to preserve the order of the 1525 // documents since each one is pushed _to the front_ of the 1526 // queue of documents to check. 1527 docs.push_front((indent, mode, *second)); 1528 docs.push_front((indent, mode, *first)); 1529 } 1530 PrintableDocument::Join3(first, second, third) => { 1531 docs.push_front((indent, mode, *third)); 1532 docs.push_front((indent, mode, *second)); 1533 docs.push_front((indent, mode, *first)); 1534 } 1535 PrintableDocument::Join4(first, second, third, fourth) => { 1536 docs.push_front((indent, mode, *fourth)); 1537 docs.push_front((indent, mode, *third)); 1538 docs.push_front((indent, mode, *second)); 1539 docs.push_front((indent, mode, *first)); 1540 } 1541 1542 PrintableDocument::Empty => (), 1543 } 1544 } 1545}