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#[derive(Clone)]
26pub enum Doc<'a, T>
27where
28 T: DocPtr<'a>,
29{
30 Nil,
32 Fail,
33
34 Text(Text<'a>),
36 TextWithLen(usize, T), HardLine,
38
39 Append(T, T), LineSuffix(T), Nest(isize, T), DedentToRoot(T), Align(T), ExpandParent, Flatten(T), BreakOrFlat(T, T), Group(T), Union(T, T), PartialUnion(T, T), #[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 #[inline]
81 pub fn as_string<U: fmt::Display>(data: U) -> Self {
82 T::ALLOCATOR.as_string(data).into_plain_doc()
83 }
84
85 #[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 #[inline]
275 pub fn as_string<U: fmt::Display>(data: U) -> Self {
276 $allocator.as_string(data).into_doc()
277 }
278
279 #[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 #[inline]
292 pub fn softline_() -> Self {
293 Self::line_().group()
294 }
295
296 #[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 #[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 #[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 #[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 #[inline]
350 pub fn group(self) -> Self {
351 DocBuilder(&$allocator, self.into()).group().into_doc()
352 }
353
354 #[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 #[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 #[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 #[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 #[inline]
422 pub fn line() -> Self {
423 Self::hardline().flat_alt(Self::space()).into()
424 }
425
426 #[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
441pub 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#[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 #[inline]
490 pub fn as_string<U: fmt::Display>(data: U) -> Self {
491 T::ALLOCATOR.as_string(data).1
492 }
493
494 #[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 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}