prettyless/
doc.rs

1use std::{borrow::Cow, fmt, ops::Deref, rc::Rc};
2
3use crate::{
4    text::Text, visitor::visit_sequence_deep, BoxAllocator, DocAllocator, DocBuilder, Pretty,
5    RcAllocator,
6};
7
8pub trait DocPtr<'a>: Deref<Target = Doc<'a, Self>> + Sized {
9    #[cfg(feature = "contextual")]
10    type ColumnFn: Deref<Target = dyn Fn(usize) -> Self + 'a> + Clone + 'a;
11    #[cfg(feature = "contextual")]
12    type WidthFn: Deref<Target = dyn Fn(isize) -> Self + 'a> + Clone + 'a;
13}
14
15pub trait StaticDoc<'a>: DocPtr<'a> {
16    type Allocator: DocAllocator<'a, Doc = Self> + 'static;
17    const ALLOCATOR: &'static Self::Allocator;
18}
19
20/// The concrete document type. This type is not meant to be used directly. Instead use the static
21/// functions on `Doc` or the methods on an `DocAllocator`.
22///
23/// The `T` parameter is used to abstract over pointers to `Doc`. See `RefDoc` and `BoxDoc` for how
24/// it is used
25#[derive(Clone)]
26pub enum Doc<'a, T>
27where
28    T: DocPtr<'a>,
29{
30    // Primitives
31    Nil,
32    Fail,
33
34    // Texts
35    Text(Text<'a>),
36    TextWithLen(usize, T), // Stores the length of a string document that is not just ascii
37    HardLine,
38
39    // Structural
40    Append(T, T),  // Sequencing
41    LineSuffix(T), // A document that is appended to the end of the current line.
42
43    // Indentation and Alignment
44    Nest(isize, T),  // Changes the indentation level
45    DedentToRoot(T), // Dedent to the root level, which is always 0
46    Align(T),        // Align to the current position
47
48    // Choices
49    ExpandParent,       // make the parent group break
50    Flatten(T),         // always flat inside
51    BreakOrFlat(T, T),  // break vs flat
52    Group(T),           // try flat vs broken
53    Union(T, T),        // alternative layouts
54    PartialUnion(T, T), // like union, but only fit on the first line
55
56    // Contextual
57    #[cfg(feature = "contextual")]
58    OnColumn(T::ColumnFn),
59    #[cfg(feature = "contextual")]
60    OnNesting(T::ColumnFn),
61}
62
63impl<'a, T> Doc<'a, T>
64where
65    T: DocPtr<'a>,
66{
67    #[inline]
68    pub fn is_nil(&self) -> bool {
69        matches!(self, Self::Nil)
70    }
71}
72
73impl<'a, T> Doc<'a, T>
74where
75    T: StaticDoc<'a>,
76{
77    /// The text `t.to_string()`.
78    ///
79    /// The given text must not contain line breaks.
80    #[inline]
81    pub fn as_string<U: fmt::Display>(data: U) -> Self {
82        T::ALLOCATOR.as_string(data).into_plain_doc()
83    }
84
85    /// The given text, which must not contain line breaks.
86    #[inline]
87    pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Self {
88        T::ALLOCATOR.text(data).into_plain_doc()
89    }
90
91    fn flat_alt<D>(self, doc: D) -> Self
92    where
93        D: Pretty<'a, T::Allocator>,
94    {
95        DocBuilder(T::ALLOCATOR, self.into())
96            .flat_alt(doc)
97            .into_plain_doc()
98    }
99}
100
101impl<'a, T> Default for Doc<'a, T>
102where
103    T: DocPtr<'a>,
104{
105    fn default() -> Self {
106        Self::Nil
107    }
108}
109
110impl<'a, T, S> From<S> for Doc<'a, T>
111where
112    T: StaticDoc<'a>,
113    S: Into<Cow<'a, str>>,
114{
115    fn from(s: S) -> Doc<'a, T> {
116        Doc::text(s)
117    }
118}
119
120impl<'a, T> fmt::Debug for Doc<'a, T>
121where
122    T: DocPtr<'a> + fmt::Debug,
123{
124    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
125        let write_compact = |f: &mut fmt::Formatter<'_>, doc: &T, name: &str| {
126            if matches!(**doc, Doc::Append(_, _)) {
127                f.write_str(name)?;
128                f.write_str("(")?;
129                doc.fmt(f)?;
130                f.write_str(")")
131            } else {
132                f.debug_tuple(name).field(doc).finish()
133            }
134        };
135
136        match self {
137            Doc::Nil => f.write_str("Nil"),
138            Doc::Fail => f.write_str("Fail"),
139
140            Doc::HardLine => f.write_str("HardLine"),
141            Doc::TextWithLen(_, d) => d.fmt(f),
142            Doc::Text(s) => s.fmt(f),
143
144            Doc::Append(..) => {
145                let mut f = f.debug_list();
146                visit_sequence_deep(self, &mut |doc| {
147                    f.entry(doc);
148                });
149                f.finish()
150            }
151            Doc::LineSuffix(ref doc) => write_compact(f, doc, "LineSuffix"),
152
153            Doc::Nest(off, ref doc) => {
154                write!(f, "Nest({off}, ",)?;
155                doc.fmt(f)?;
156                write!(f, ")")
157            }
158            Doc::DedentToRoot(ref doc) => write_compact(f, doc, "DedentToRoot"),
159            Doc::Align(ref doc) => write_compact(f, doc, "Align"),
160
161            Doc::ExpandParent => f.write_str("ExpandParent"),
162            Doc::Flatten(ref doc) => write_compact(f, doc, "Flatten"),
163            Doc::BreakOrFlat(ref x, ref y) => match (&**x, &**y) {
164                (Doc::HardLine, Doc::Text(Text::Borrowed(" "))) => f.write_str("LineOrSpace"),
165                (Doc::HardLine, Doc::Nil) => f.write_str("LineOrNil"),
166                (_, Doc::Nil) => f.debug_tuple("WhenBreak").field(x).finish(),
167                (Doc::Nil, _) => f.debug_tuple("WhenFlat").field(y).finish(),
168                _ => f.debug_tuple("FlatOrBreak").field(y).field(x).finish(),
169            },
170            Doc::Group(ref doc) => match &**doc {
171                Doc::BreakOrFlat(x, y)
172                    if matches!(
173                        (&**x, &**y),
174                        (Doc::HardLine, Doc::Text(Text::Borrowed(" ")))
175                    ) =>
176                {
177                    f.write_str("SoftLineOrSpace")
178                }
179                Doc::BreakOrFlat(x, y) if matches!((&**x, &**y), (Doc::HardLine, Doc::Nil)) => {
180                    f.write_str("SoftLineOrNil")
181                }
182                _ => write_compact(f, doc, "Group"),
183            },
184            Doc::Union(ref l, ref r) => f.debug_tuple("Union").field(l).field(r).finish(),
185            Doc::PartialUnion(ref l, ref r) => {
186                f.debug_tuple("PartialUnion").field(l).field(r).finish()
187            }
188
189            #[cfg(feature = "contextual")]
190            Doc::OnColumn(_) => f.write_str("OnColumn(..)"),
191            #[cfg(feature = "contextual")]
192            Doc::OnNesting(_) => f.write_str("OnNesting(..)"),
193        }
194    }
195}
196
197macro_rules! impl_doc {
198    ($name: ident, $ptr: ident, $allocator: ident) => {
199        #[derive(Clone)]
200        pub struct $name<'a>($ptr<Doc<'a, $name<'a>>>);
201
202        impl<'a> fmt::Debug for $name<'a> {
203            fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
204                self.0.fmt(f)
205            }
206        }
207
208        impl<'a> $name<'a> {
209            pub fn new(doc: Doc<'a, $name<'a>>) -> $name<'a> {
210                $name($ptr::new(doc))
211            }
212        }
213
214        impl<'a> From<Doc<'a, Self>> for $name<'a> {
215            fn from(doc: Doc<'a, $name<'a>>) -> $name<'a> {
216                $name::new(doc)
217            }
218        }
219
220        impl<'a> Deref for $name<'a> {
221            type Target = Doc<'a, $name<'a>>;
222
223            fn deref(&self) -> &Self::Target {
224                &self.0
225            }
226        }
227
228        impl<'a> DocAllocator<'a> for $allocator
229        where
230
231        {
232            type Doc = $name<'a>;
233
234            #[inline]
235            fn alloc(&'a self, doc: Doc<'a, Self::Doc>) -> Self::Doc {
236                $name::new(doc)
237            }
238
239            #[cfg(feature = "contextual")]
240            fn alloc_column_fn(
241                &'a self,
242                f: impl Fn(usize) -> Self::Doc + 'a,
243            ) -> <Self::Doc as DocPtr<'a>>::ColumnFn {
244                Rc::new(f)
245            }
246
247            #[cfg(feature = "contextual")]
248            fn alloc_width_fn(
249                &'a self,
250                f: impl Fn(isize) -> Self::Doc + 'a,
251            ) -> <Self::Doc as DocPtr<'a>>::WidthFn {
252                Rc::new(f)
253            }
254        }
255
256        impl<'a> DocPtr<'a> for $name<'a> {
257            #[cfg(feature = "contextual")]
258            type ColumnFn = std::rc::Rc<dyn Fn(usize) -> Self + 'a>;
259            #[cfg(feature = "contextual")]
260            type WidthFn = std::rc::Rc<dyn Fn(isize) -> Self + 'a>;
261        }
262
263        impl<'a> StaticDoc<'a> for $name<'a> {
264            type Allocator = $allocator;
265            const ALLOCATOR: &'static Self::Allocator = &$allocator;
266        }
267
268        impl_doc_methods!($name ('a) where () where ());
269
270        impl<'a> $name<'a> {
271            /// The text `t.to_string()`.
272            ///
273            /// The given text must not contain line breaks.
274            #[inline]
275            pub fn as_string<U: fmt::Display>(data: U) -> Self {
276                $allocator.as_string(data).into_doc()
277            }
278
279            /// The given text, which must not contain line breaks.
280            #[inline]
281            pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Self {
282                $allocator.text(data).into_doc()
283            }
284
285            #[inline]
286            pub fn softline() -> Self {
287                Self::line().group()
288            }
289
290            /// A `softline_` acts like `nil` if the document fits the page, otherwise like `line_`
291            #[inline]
292            pub fn softline_() -> Self {
293                Self::line_().group()
294            }
295
296            /// Append the given document after this document.
297            #[inline]
298            pub fn append<D>(self, that: D) -> Self
299            where
300                D: Pretty<'a, $allocator>,
301            {
302                DocBuilder(&$allocator, self.into()).append(that).into_doc()
303            }
304
305            /// A single document concatenating all the given documents.
306            #[inline]
307            pub fn concat<I>(docs: I) -> Self
308            where
309                I: IntoIterator,
310                I::Item: Pretty<'a, $allocator>,
311            {
312                $allocator.concat(docs).into_doc()
313            }
314
315            /// A single document interspersing the given separator `S` between the given documents.  For
316            /// example, if the documents are `[A, B, C, ..., Z]`, this yields `[A, S, B, S, C, S, ..., S, Z]`.
317            ///
318            /// Compare [the `intersperse` method from the `itertools` crate](https://docs.rs/itertools/0.5.9/itertools/trait.Itertools.html#method.intersperse).
319            ///
320            /// NOTE: The separator type, `S` may need to be cloned. Consider using cheaply cloneable ptr
321            /// like `RefDoc` or `RcDoc`
322            #[inline]
323            pub fn intersperse<I, S>(docs: I, separator: S) -> Self
324            where
325                I: IntoIterator,
326                I::Item: Pretty<'a, $allocator>,
327                S: Pretty<'a, $allocator> + Clone,
328            {
329                $allocator.intersperse(docs, separator).into_doc()
330            }
331
332            /// Acts as `self` when laid out on multiple lines and acts as `that` when laid out on a single line.
333            #[inline]
334            pub fn flat_alt<D>(self, doc: D) -> Self
335            where
336                D: Pretty<'a, $allocator>,
337            {
338                DocBuilder(&$allocator, self.into())
339                    .flat_alt(doc)
340                    .into_doc()
341            }
342
343            /// Mark this document as a group.
344            ///
345            /// Groups are layed out on a single line if possible.  Within a group, all basic documents with
346            /// several possible layouts are assigned the same layout, that is, they are all layed out
347            /// horizontally and combined into a one single line, or they are each layed out on their own
348            /// line.
349            #[inline]
350            pub fn group(self) -> Self {
351                DocBuilder(&$allocator, self.into()).group().into_doc()
352            }
353
354            /// Increase the indentation level of this document.
355            #[inline]
356            pub fn nest(self, offset: isize) -> Self {
357                DocBuilder(&$allocator, self.into()).nest(offset).into_doc()
358            }
359
360            #[inline]
361            pub fn union<D>(self, other: D) -> Self
362            where
363                D: Into<BuildDoc<'a, Self>>,
364            {
365                DocBuilder(&$allocator, self.into()).union(other).into_doc()
366            }
367
368            #[cfg(feature = "contextual")]
369            #[inline]
370            pub fn column(f: impl Fn(usize) -> Self + 'static) -> Self {
371                DocBuilder(&$allocator, Doc::OnColumn($allocator.alloc_column_fn(f)).into()).into_doc()
372            }
373
374            #[cfg(feature = "contextual")]
375            #[inline]
376            pub fn nesting(f: impl Fn(usize) -> Self + 'static) -> Self {
377                DocBuilder(&$allocator, Doc::OnNesting($allocator.alloc_column_fn(f)).into()).into_doc()
378            }
379        }
380    };
381}
382
383macro_rules! impl_doc_methods {
384    ($name: ident ( $($params: tt)* ) where ( $($where_: tt)* ) where ( $($where_2: tt)* )) => {
385        impl< $($params)* > $name< $($params)* >
386            where $($where_)*
387        {
388            /// An empty document.
389            #[inline]
390            pub fn nil() -> Self {
391                Doc::Nil.into()
392            }
393
394            #[inline]
395            pub fn fail() -> Self {
396                Doc::Fail.into()
397            }
398
399            /// A single hardline.
400            #[inline]
401            pub fn hardline() -> Self {
402                Doc::HardLine.into()
403            }
404
405            #[inline]
406            pub fn space() -> Self {
407                Doc::Text(Text::Borrowed(" ")).into()
408            }
409
410            /// Make the parent group break
411            #[inline]
412            pub fn expand_parent() -> Self {
413                Doc::ExpandParent.into()
414            }
415        }
416
417        impl< $($params)* > $name< $($params)* >
418            where $($where_2)*
419        {
420            /// A line acts like a `\n` but behaves like `space` if it is grouped on a single line.
421            #[inline]
422            pub fn line() -> Self {
423                Self::hardline().flat_alt(Self::space()).into()
424            }
425
426            /// Acts like `line` but behaves like `nil` if grouped on a single line
427            #[inline]
428            pub fn line_() -> Self {
429                Self::hardline().flat_alt(Self::nil()).into()
430            }
431        }
432    };
433}
434
435impl_doc!(BoxDoc, Box, BoxAllocator);
436impl_doc!(RcDoc, Rc, RcAllocator);
437
438impl_doc_methods!(Doc ('a, D) where (D: DocPtr<'a>) where (D: StaticDoc<'a>));
439impl_doc_methods!(BuildDoc ('a, D) where (D: DocPtr<'a>) where (D: StaticDoc<'a>));
440
441/// Newtype wrapper for `&Doc`
442pub struct RefDoc<'a>(pub &'a Doc<'a, RefDoc<'a>>);
443
444impl<'a> DocPtr<'a> for RefDoc<'a> {
445    #[cfg(feature = "contextual")]
446    type ColumnFn = &'a (dyn Fn(usize) -> Self + 'a);
447    #[cfg(feature = "contextual")]
448    type WidthFn = &'a (dyn Fn(isize) -> Self + 'a);
449}
450
451impl Copy for RefDoc<'_> {}
452impl Clone for RefDoc<'_> {
453    fn clone(&self) -> Self {
454        *self
455    }
456}
457
458impl fmt::Debug for RefDoc<'_> {
459    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
460        self.0.fmt(f)
461    }
462}
463
464impl<'a> Deref for RefDoc<'a> {
465    type Target = Doc<'a, RefDoc<'a>>;
466
467    fn deref(&self) -> &Self::Target {
468        self.0
469    }
470}
471
472/// Either a `Doc` or a pointer to a `Doc` (`D`)
473#[derive(Clone)]
474pub enum BuildDoc<'a, D>
475where
476    D: DocPtr<'a>,
477{
478    DocPtr(D),
479    Doc(Doc<'a, D>),
480}
481
482impl<'a, T> BuildDoc<'a, T>
483where
484    T: StaticDoc<'a>,
485{
486    /// The text `t.to_string()`.
487    ///
488    /// The given text must not contain line breaks.
489    #[inline]
490    pub fn as_string<U: fmt::Display>(data: U) -> Self {
491        T::ALLOCATOR.as_string(data).1
492    }
493
494    /// The given text, which must not contain line breaks.
495    #[inline]
496    pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Self {
497        T::ALLOCATOR.text(data).1
498    }
499
500    fn flat_alt<D>(self, doc: D) -> Self
501    where
502        D: Pretty<'a, T::Allocator>,
503    {
504        DocBuilder(T::ALLOCATOR, self).flat_alt(doc).1
505    }
506}
507
508impl<'a, D> Default for BuildDoc<'a, D>
509where
510    D: DocPtr<'a>,
511{
512    fn default() -> Self {
513        Self::Doc(Doc::default())
514    }
515}
516
517impl<'a, D> fmt::Debug for BuildDoc<'a, D>
518where
519    D: DocPtr<'a> + fmt::Debug,
520{
521    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
522        (**self).fmt(f)
523    }
524}
525
526impl<'a, D> Deref for BuildDoc<'a, D>
527where
528    D: DocPtr<'a>,
529{
530    type Target = Doc<'a, D>;
531    fn deref(&self) -> &Self::Target {
532        match self {
533            BuildDoc::DocPtr(d) => d,
534            BuildDoc::Doc(d) => d,
535        }
536    }
537}
538
539impl<'a> From<RefDoc<'a>> for BuildDoc<'a, RefDoc<'a>> {
540    fn from(s: RefDoc<'a>) -> Self {
541        BuildDoc::DocPtr(s)
542    }
543}
544
545impl<'a> From<BoxDoc<'a>> for BuildDoc<'a, BoxDoc<'a>> {
546    fn from(s: BoxDoc<'a>) -> Self {
547        BuildDoc::DocPtr(s)
548    }
549}
550
551impl<'a> From<RcDoc<'a>> for BuildDoc<'a, RcDoc<'a>> {
552    fn from(s: RcDoc<'a>) -> Self {
553        BuildDoc::DocPtr(s)
554    }
555}
556
557impl<'a, T> From<Doc<'a, T>> for BuildDoc<'a, T>
558where
559    T: DocPtr<'a>,
560{
561    fn from(s: Doc<'a, T>) -> Self {
562        BuildDoc::Doc(s)
563    }
564}
565
566impl<'a, T> From<String> for BuildDoc<'a, T>
567where
568    T: StaticDoc<'a>,
569{
570    fn from(s: String) -> Self {
571        BuildDoc::Doc(Doc::text(s))
572    }
573}
574
575impl<'a, T> From<&'a str> for BuildDoc<'a, T>
576where
577    T: StaticDoc<'a>,
578{
579    fn from(s: &'a str) -> Self {
580        BuildDoc::Doc(Doc::text(s))
581    }
582}
583
584impl<'a, T> From<&'a String> for BuildDoc<'a, T>
585where
586    T: StaticDoc<'a>,
587{
588    fn from(s: &'a String) -> Self {
589        BuildDoc::Doc(Doc::text(s))
590    }
591}
592
593impl<'a, T, S> From<Option<S>> for BuildDoc<'a, T>
594where
595    T: DocPtr<'a>,
596    S: Into<BuildDoc<'a, T>>,
597{
598    fn from(s: Option<S>) -> Self {
599        match s {
600            Some(s) => s.into(),
601            None => BuildDoc::Doc(Doc::Nil),
602        }
603    }
604}
605
606#[cfg(test)]
607mod tests {
608    use crate::*;
609
610    #[cfg(target_pointer_width = "64")]
611    #[test]
612    fn doc_size() {
613        // Safeguard against accidentally growing Doc
614        assert_eq!(8 * 3, std::mem::size_of::<Doc<RefDoc>>());
615    }
616
617    #[test]
618    fn debug_concat() {
619        let a = Arena::new();
620        let doc = (a.text("1") + a.text("2")) + a.text("3") + a.text("4");
621        assert_eq!(
622            format!("{doc:#?}"),
623            r#"[
624    "1",
625    "2",
626    "3",
627    "4",
628]"#
629        )
630    }
631
632    #[test]
633    fn doc_is_nil() {
634        let arena = Arena::new();
635        assert!(arena.nil().is_nil());
636        assert!((arena.nil() + arena.nil()).is_nil());
637        assert!(arena.text("").is_nil());
638        assert!(!arena.text(" ").is_nil());
639    }
640}