use std::{borrow::Cow, fmt, ops::Deref, rc::Rc};
use crate::{
text::Text, visitor::visit_sequence_deep, BoxAllocator, DocAllocator, DocBuilder, Pretty,
RcAllocator,
};
pub trait DocPtr<'a>: Deref<Target = Doc<'a, Self>> + Sized {
#[cfg(feature = "contextual")]
type ColumnFn: Deref<Target = dyn Fn(usize) -> Self + 'a> + Clone + 'a;
#[cfg(feature = "contextual")]
type WidthFn: Deref<Target = dyn Fn(isize) -> Self + 'a> + Clone + 'a;
}
pub trait StaticDoc<'a>: DocPtr<'a> {
type Allocator: DocAllocator<'a, Doc = Self> + 'static;
const ALLOCATOR: &'static Self::Allocator;
}
#[derive(Clone)]
pub enum Doc<'a, T>
where
T: DocPtr<'a>,
{
Nil,
Fail,
Text(Text<'a>),
TextWithLen(usize, T), HardLine,
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")]
OnColumn(T::ColumnFn),
#[cfg(feature = "contextual")]
OnNesting(T::ColumnFn),
}
impl<'a, T> Doc<'a, T>
where
T: DocPtr<'a>,
{
#[inline]
pub fn is_nil(&self) -> bool {
matches!(self, Self::Nil)
}
}
impl<'a, T> Doc<'a, T>
where
T: StaticDoc<'a>,
{
#[inline]
pub fn as_string<U: fmt::Display>(data: U) -> Self {
T::ALLOCATOR.as_string(data).into_plain_doc()
}
#[inline]
pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Self {
T::ALLOCATOR.text(data).into_plain_doc()
}
fn flat_alt<D>(self, doc: D) -> Self
where
D: Pretty<'a, T::Allocator>,
{
DocBuilder(T::ALLOCATOR, self.into())
.flat_alt(doc)
.into_plain_doc()
}
}
impl<'a, T> Default for Doc<'a, T>
where
T: DocPtr<'a>,
{
fn default() -> Self {
Self::Nil
}
}
impl<'a, T, S> From<S> for Doc<'a, T>
where
T: StaticDoc<'a>,
S: Into<Cow<'a, str>>,
{
fn from(s: S) -> Doc<'a, T> {
Doc::text(s)
}
}
impl<'a, T> fmt::Debug for Doc<'a, T>
where
T: DocPtr<'a> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
let write_compact = |f: &mut fmt::Formatter<'_>, doc: &T, name: &str| {
if matches!(**doc, Doc::Append(_, _)) {
f.write_str(name)?;
f.write_str("(")?;
doc.fmt(f)?;
f.write_str(")")
} else {
f.debug_tuple(name).field(doc).finish()
}
};
match self {
Doc::Nil => f.write_str("Nil"),
Doc::Fail => f.write_str("Fail"),
Doc::HardLine => f.write_str("HardLine"),
Doc::TextWithLen(_, d) => d.fmt(f),
Doc::Text(s) => s.fmt(f),
Doc::Append(..) => {
let mut f = f.debug_list();
visit_sequence_deep(self, &mut |doc| {
f.entry(doc);
});
f.finish()
}
Doc::LineSuffix(ref doc) => write_compact(f, doc, "LineSuffix"),
Doc::Nest(off, ref doc) => {
write!(f, "Nest({off}, ",)?;
doc.fmt(f)?;
write!(f, ")")
}
Doc::DedentToRoot(ref doc) => write_compact(f, doc, "DedentToRoot"),
Doc::Align(ref doc) => write_compact(f, doc, "Align"),
Doc::ExpandParent => f.write_str("ExpandParent"),
Doc::Flatten(ref doc) => write_compact(f, doc, "Flatten"),
Doc::BreakOrFlat(ref x, ref y) => match (&**x, &**y) {
(Doc::HardLine, Doc::Text(Text::Borrowed(" "))) => f.write_str("LineOrSpace"),
(Doc::HardLine, Doc::Nil) => f.write_str("LineOrNil"),
(_, Doc::Nil) => f.debug_tuple("WhenBreak").field(x).finish(),
(Doc::Nil, _) => f.debug_tuple("WhenFlat").field(y).finish(),
_ => f.debug_tuple("FlatOrBreak").field(y).field(x).finish(),
},
Doc::Group(ref doc) => match &**doc {
Doc::BreakOrFlat(x, y)
if matches!(
(&**x, &**y),
(Doc::HardLine, Doc::Text(Text::Borrowed(" ")))
) =>
{
f.write_str("SoftLineOrSpace")
}
Doc::BreakOrFlat(x, y) if matches!((&**x, &**y), (Doc::HardLine, Doc::Nil)) => {
f.write_str("SoftLineOrNil")
}
_ => write_compact(f, doc, "Group"),
},
Doc::Union(ref l, ref r) => f.debug_tuple("Union").field(l).field(r).finish(),
Doc::PartialUnion(ref l, ref r) => {
f.debug_tuple("PartialUnion").field(l).field(r).finish()
}
#[cfg(feature = "contextual")]
Doc::OnColumn(_) => f.write_str("OnColumn(..)"),
#[cfg(feature = "contextual")]
Doc::OnNesting(_) => f.write_str("OnNesting(..)"),
}
}
}
macro_rules! impl_doc {
($name: ident, $ptr: ident, $allocator: ident) => {
#[derive(Clone)]
pub struct $name<'a>($ptr<Doc<'a, $name<'a>>>);
impl<'a> fmt::Debug for $name<'a> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> $name<'a> {
pub fn new(doc: Doc<'a, $name<'a>>) -> $name<'a> {
$name($ptr::new(doc))
}
}
impl<'a> From<Doc<'a, Self>> for $name<'a> {
fn from(doc: Doc<'a, $name<'a>>) -> $name<'a> {
$name::new(doc)
}
}
impl<'a> Deref for $name<'a> {
type Target = Doc<'a, $name<'a>>;
fn deref(&self) -> &Self::Target {
&self.0
}
}
impl<'a> DocAllocator<'a> for $allocator
where
{
type Doc = $name<'a>;
#[inline]
fn alloc(&'a self, doc: Doc<'a, Self::Doc>) -> Self::Doc {
$name::new(doc)
}
#[cfg(feature = "contextual")]
fn alloc_column_fn(
&'a self,
f: impl Fn(usize) -> Self::Doc + 'a,
) -> <Self::Doc as DocPtr<'a>>::ColumnFn {
Rc::new(f)
}
#[cfg(feature = "contextual")]
fn alloc_width_fn(
&'a self,
f: impl Fn(isize) -> Self::Doc + 'a,
) -> <Self::Doc as DocPtr<'a>>::WidthFn {
Rc::new(f)
}
}
impl<'a> DocPtr<'a> for $name<'a> {
#[cfg(feature = "contextual")]
type ColumnFn = std::rc::Rc<dyn Fn(usize) -> Self + 'a>;
#[cfg(feature = "contextual")]
type WidthFn = std::rc::Rc<dyn Fn(isize) -> Self + 'a>;
}
impl<'a> StaticDoc<'a> for $name<'a> {
type Allocator = $allocator;
const ALLOCATOR: &'static Self::Allocator = &$allocator;
}
impl_doc_methods!($name ('a) where () where ());
impl<'a> $name<'a> {
#[inline]
pub fn as_string<U: fmt::Display>(data: U) -> Self {
$allocator.as_string(data).into_doc()
}
#[inline]
pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Self {
$allocator.text(data).into_doc()
}
#[inline]
pub fn softline() -> Self {
Self::line().group()
}
#[inline]
pub fn softline_() -> Self {
Self::line_().group()
}
#[inline]
pub fn append<D>(self, that: D) -> Self
where
D: Pretty<'a, $allocator>,
{
DocBuilder(&$allocator, self.into()).append(that).into_doc()
}
#[inline]
pub fn concat<I>(docs: I) -> Self
where
I: IntoIterator,
I::Item: Pretty<'a, $allocator>,
{
$allocator.concat(docs).into_doc()
}
#[inline]
pub fn intersperse<I, S>(docs: I, separator: S) -> Self
where
I: IntoIterator,
I::Item: Pretty<'a, $allocator>,
S: Pretty<'a, $allocator> + Clone,
{
$allocator.intersperse(docs, separator).into_doc()
}
#[inline]
pub fn flat_alt<D>(self, doc: D) -> Self
where
D: Pretty<'a, $allocator>,
{
DocBuilder(&$allocator, self.into())
.flat_alt(doc)
.into_doc()
}
#[inline]
pub fn group(self) -> Self {
DocBuilder(&$allocator, self.into()).group().into_doc()
}
#[inline]
pub fn nest(self, offset: isize) -> Self {
DocBuilder(&$allocator, self.into()).nest(offset).into_doc()
}
#[inline]
pub fn union<D>(self, other: D) -> Self
where
D: Into<BuildDoc<'a, Self>>,
{
DocBuilder(&$allocator, self.into()).union(other).into_doc()
}
#[cfg(feature = "contextual")]
#[inline]
pub fn column(f: impl Fn(usize) -> Self + 'static) -> Self {
DocBuilder(&$allocator, Doc::OnColumn($allocator.alloc_column_fn(f)).into()).into_doc()
}
#[cfg(feature = "contextual")]
#[inline]
pub fn nesting(f: impl Fn(usize) -> Self + 'static) -> Self {
DocBuilder(&$allocator, Doc::OnNesting($allocator.alloc_column_fn(f)).into()).into_doc()
}
}
};
}
macro_rules! impl_doc_methods {
($name: ident ( $($params: tt)* ) where ( $($where_: tt)* ) where ( $($where_2: tt)* )) => {
impl< $($params)* > $name< $($params)* >
where $($where_)*
{
#[inline]
pub fn nil() -> Self {
Doc::Nil.into()
}
#[inline]
pub fn fail() -> Self {
Doc::Fail.into()
}
#[inline]
pub fn hardline() -> Self {
Doc::HardLine.into()
}
#[inline]
pub fn space() -> Self {
Doc::Text(Text::Borrowed(" ")).into()
}
#[inline]
pub fn expand_parent() -> Self {
Doc::ExpandParent.into()
}
}
impl< $($params)* > $name< $($params)* >
where $($where_2)*
{
#[inline]
pub fn line() -> Self {
Self::hardline().flat_alt(Self::space()).into()
}
#[inline]
pub fn line_() -> Self {
Self::hardline().flat_alt(Self::nil()).into()
}
}
};
}
impl_doc!(BoxDoc, Box, BoxAllocator);
impl_doc!(RcDoc, Rc, RcAllocator);
impl_doc_methods!(Doc ('a, D) where (D: DocPtr<'a>) where (D: StaticDoc<'a>));
impl_doc_methods!(BuildDoc ('a, D) where (D: DocPtr<'a>) where (D: StaticDoc<'a>));
pub struct RefDoc<'a>(pub &'a Doc<'a, RefDoc<'a>>);
impl<'a> DocPtr<'a> for RefDoc<'a> {
#[cfg(feature = "contextual")]
type ColumnFn = &'a (dyn Fn(usize) -> Self + 'a);
#[cfg(feature = "contextual")]
type WidthFn = &'a (dyn Fn(isize) -> Self + 'a);
}
impl Copy for RefDoc<'_> {}
impl Clone for RefDoc<'_> {
fn clone(&self) -> Self {
*self
}
}
impl fmt::Debug for RefDoc<'_> {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
impl<'a> Deref for RefDoc<'a> {
type Target = Doc<'a, RefDoc<'a>>;
fn deref(&self) -> &Self::Target {
self.0
}
}
#[derive(Clone)]
pub enum BuildDoc<'a, D>
where
D: DocPtr<'a>,
{
DocPtr(D),
Doc(Doc<'a, D>),
}
impl<'a, T> BuildDoc<'a, T>
where
T: StaticDoc<'a>,
{
#[inline]
pub fn as_string<U: fmt::Display>(data: U) -> Self {
T::ALLOCATOR.as_string(data).1
}
#[inline]
pub fn text<U: Into<Cow<'a, str>>>(data: U) -> Self {
T::ALLOCATOR.text(data).1
}
fn flat_alt<D>(self, doc: D) -> Self
where
D: Pretty<'a, T::Allocator>,
{
DocBuilder(T::ALLOCATOR, self).flat_alt(doc).1
}
}
impl<'a, D> Default for BuildDoc<'a, D>
where
D: DocPtr<'a>,
{
fn default() -> Self {
Self::Doc(Doc::default())
}
}
impl<'a, D> fmt::Debug for BuildDoc<'a, D>
where
D: DocPtr<'a> + fmt::Debug,
{
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
(**self).fmt(f)
}
}
impl<'a, D> Deref for BuildDoc<'a, D>
where
D: DocPtr<'a>,
{
type Target = Doc<'a, D>;
fn deref(&self) -> &Self::Target {
match self {
BuildDoc::DocPtr(d) => d,
BuildDoc::Doc(d) => d,
}
}
}
impl<'a> From<RefDoc<'a>> for BuildDoc<'a, RefDoc<'a>> {
fn from(s: RefDoc<'a>) -> Self {
BuildDoc::DocPtr(s)
}
}
impl<'a> From<BoxDoc<'a>> for BuildDoc<'a, BoxDoc<'a>> {
fn from(s: BoxDoc<'a>) -> Self {
BuildDoc::DocPtr(s)
}
}
impl<'a> From<RcDoc<'a>> for BuildDoc<'a, RcDoc<'a>> {
fn from(s: RcDoc<'a>) -> Self {
BuildDoc::DocPtr(s)
}
}
impl<'a, T> From<Doc<'a, T>> for BuildDoc<'a, T>
where
T: DocPtr<'a>,
{
fn from(s: Doc<'a, T>) -> Self {
BuildDoc::Doc(s)
}
}
impl<'a, T> From<String> for BuildDoc<'a, T>
where
T: StaticDoc<'a>,
{
fn from(s: String) -> Self {
BuildDoc::Doc(Doc::text(s))
}
}
impl<'a, T> From<&'a str> for BuildDoc<'a, T>
where
T: StaticDoc<'a>,
{
fn from(s: &'a str) -> Self {
BuildDoc::Doc(Doc::text(s))
}
}
impl<'a, T> From<&'a String> for BuildDoc<'a, T>
where
T: StaticDoc<'a>,
{
fn from(s: &'a String) -> Self {
BuildDoc::Doc(Doc::text(s))
}
}
impl<'a, T, S> From<Option<S>> for BuildDoc<'a, T>
where
T: DocPtr<'a>,
S: Into<BuildDoc<'a, T>>,
{
fn from(s: Option<S>) -> Self {
match s {
Some(s) => s.into(),
None => BuildDoc::Doc(Doc::Nil),
}
}
}
#[cfg(test)]
mod tests {
use crate::*;
#[cfg(target_pointer_width = "64")]
#[test]
fn doc_size() {
assert_eq!(8 * 3, std::mem::size_of::<Doc<RefDoc>>());
}
#[test]
fn debug_concat() {
let a = Arena::new();
let doc = (a.text("1") + a.text("2")) + a.text("3") + a.text("4");
assert_eq!(
format!("{doc:#?}"),
r#"[
"1",
"2",
"3",
"4",
]"#
)
}
#[test]
fn doc_is_nil() {
let arena = Arena::new();
assert!(arena.nil().is_nil());
assert!((arena.nil() + arena.nil()).is_nil());
assert!(arena.text("").is_nil());
assert!(!arena.text(" ").is_nil());
}
}