use alloc::borrow::Cow;
use alloc::rc::Rc;
use alloc::string::String;
use alloc::vec::Vec;
#[derive(Clone)]
pub struct Doc(pub(crate) Rc<Node>);
pub(crate) enum Node {
Nil,
Text(Cow<'static, str>, usize),
Line,
SoftLine,
HardLine,
Cat(Doc, Doc),
Nest(isize, Doc),
Group(Doc),
}
impl Doc {
#[inline]
#[must_use]
pub fn nil() -> Doc {
Doc(Rc::new(Node::Nil))
}
#[inline]
#[must_use]
pub fn text(s: impl Into<Cow<'static, str>>) -> Doc {
let s = s.into();
let width = s.chars().count();
Doc(Rc::new(Node::Text(s, width)))
}
#[inline]
#[must_use]
pub fn line() -> Doc {
Doc(Rc::new(Node::Line))
}
#[inline]
#[must_use]
pub fn softline() -> Doc {
Doc(Rc::new(Node::SoftLine))
}
#[inline]
#[must_use]
pub fn hardline() -> Doc {
Doc(Rc::new(Node::HardLine))
}
#[inline]
#[must_use]
pub fn append(self, other: Doc) -> Doc {
Doc(Rc::new(Node::Cat(self, other)))
}
#[inline]
#[must_use]
pub fn nest(self, indent: isize) -> Doc {
Doc(Rc::new(Node::Nest(indent, self)))
}
#[inline]
#[must_use]
pub fn group(self) -> Doc {
Doc(Rc::new(Node::Group(self)))
}
#[must_use]
pub fn concat(docs: impl IntoIterator<Item = Doc>) -> Doc {
let mut iter = docs.into_iter();
let mut acc = match iter.next() {
Some(first) => first,
None => return Doc::nil(),
};
for doc in iter {
acc = acc.append(doc);
}
acc
}
#[must_use]
pub fn join(sep: Doc, docs: impl IntoIterator<Item = Doc>) -> Doc {
let mut iter = docs.into_iter();
let mut acc = match iter.next() {
Some(first) => first,
None => return Doc::nil(),
};
for doc in iter {
acc = acc.append(sep.clone()).append(doc);
}
acc
}
#[must_use]
pub fn render(&self, width: usize) -> String {
let mut out = String::new();
let _ = crate::render::layout(self, width, &mut out);
out
}
pub fn render_into<W: core::fmt::Write>(&self, width: usize, out: &mut W) -> core::fmt::Result {
crate::render::layout(self, width, out)
}
#[cfg(feature = "std")]
#[cfg_attr(docsrs, doc(cfg(feature = "std")))]
pub fn render_writer<W: std::io::Write>(
&self,
width: usize,
out: &mut W,
) -> std::io::Result<()> {
crate::render::layout_io(self, width, out)
}
}
impl Default for Doc {
#[inline]
fn default() -> Self {
Doc::nil()
}
}
impl From<&'static str> for Doc {
#[inline]
fn from(s: &'static str) -> Self {
Doc::text(s)
}
}
impl From<String> for Doc {
#[inline]
fn from(s: String) -> Self {
Doc::text(s)
}
}
impl Drop for Doc {
fn drop(&mut self) {
if matches!(
&*self.0,
Node::Nil | Node::Text(..) | Node::Line | Node::SoftLine | Node::HardLine
) {
return;
}
if Rc::get_mut(&mut self.0).is_none() {
return;
}
let nil = Rc::new(Node::Nil);
let mut stack: Vec<Rc<Node>> = Vec::new();
take_children(&mut self.0, &nil, &mut stack);
while let Some(mut node) = stack.pop() {
take_children(&mut node, &nil, &mut stack);
}
}
}
fn take_children(rc: &mut Rc<Node>, nil: &Rc<Node>, stack: &mut Vec<Rc<Node>>) {
let Some(node) = Rc::get_mut(rc) else { return };
match node {
Node::Cat(a, b) => {
stack.push(core::mem::replace(&mut a.0, nil.clone()));
stack.push(core::mem::replace(&mut b.0, nil.clone()));
}
Node::Nest(_, x) | Node::Group(x) => {
stack.push(core::mem::replace(&mut x.0, nil.clone()));
}
Node::Nil | Node::Text(..) | Node::Line | Node::SoftLine | Node::HardLine => {}
}
}
impl core::fmt::Debug for Doc {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
enum Step {
Node(Doc),
Str(&'static str),
}
let mut stack = Vec::from([Step::Node(self.clone())]);
while let Some(step) = stack.pop() {
match step {
Step::Str(s) => f.write_str(s)?,
Step::Node(doc) => match &*doc.0 {
Node::Nil => f.write_str("Nil")?,
Node::Text(s, _) => write!(f, "Text({s:?})")?,
Node::Line => f.write_str("Line")?,
Node::SoftLine => f.write_str("SoftLine")?,
Node::HardLine => f.write_str("HardLine")?,
Node::Cat(a, b) => {
f.write_str("Cat(")?;
stack.push(Step::Str(")"));
stack.push(Step::Node(b.clone()));
stack.push(Step::Str(", "));
stack.push(Step::Node(a.clone()));
}
Node::Nest(i, x) => {
write!(f, "Nest({i}, ")?;
stack.push(Step::Str(")"));
stack.push(Step::Node(x.clone()));
}
Node::Group(x) => {
f.write_str("Group(")?;
stack.push(Step::Str(")"));
stack.push(Step::Node(x.clone()));
}
},
}
}
Ok(())
}
}