Fork of daniellemaywood.uk/gleam — Wasm codegen work
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}