mod builder;
mod render;
pub use builder::{hardline, hardspace, line, linebreak, newline};
pub use render::RenderConfig;
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
pub enum Spacing {
Softbreak,
Break,
Hardspace,
Softspace,
Space,
Hardline,
Emptyline,
Newlines(usize),
}
impl Spacing {
pub(super) fn merge(self, other: Self) -> Self {
use Spacing::{Break, Emptyline, Hardspace, Newlines, Softbreak, Softspace, Space};
let (lo, hi) = if self <= other {
(self, other)
} else {
(other, self)
};
match (lo, hi) {
(Break, Softspace | Hardspace) => Space,
(Softbreak, Hardspace) => Softspace,
(Newlines(x), Newlines(y)) => Newlines(x + y),
(Emptyline, Newlines(x)) => Newlines(x + 2),
(Hardspace, Newlines(x)) => Newlines(x),
(_, Newlines(x)) => Newlines(x + 1),
_ => hi,
}
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum GroupKind {
Regular,
Priority,
Transparent,
}
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum TextKind {
Regular,
Comment,
TrailingComment,
Trailing,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub enum Elem {
Text(usize, usize, TextKind, String),
Spacing(Spacing),
Group(GroupKind, Doc),
Nest(isize, isize),
}
#[derive(Debug, Default, Clone, PartialEq, Eq)]
pub struct Doc(pub(crate) Vec<Elem>);
impl Doc {
pub const fn new() -> Self {
Self(Vec::new())
}
pub fn push_raw(&mut self, e: Elem) -> &mut Self {
self.0.push(e);
self
}
}
impl std::ops::Deref for Doc {
type Target = [Elem];
fn deref(&self) -> &[Elem] {
&self.0
}
}
impl IntoIterator for Doc {
type Item = Elem;
type IntoIter = std::vec::IntoIter<Elem>;
fn into_iter(self) -> Self::IntoIter {
self.0.into_iter()
}
}
impl Extend<Elem> for Doc {
fn extend<I: IntoIterator<Item = Elem>>(&mut self, iter: I) {
self.0.extend(iter);
}
}
impl From<Vec<Elem>> for Doc {
fn from(v: Vec<Elem>) -> Self {
Self(v)
}
}
#[cfg(any(test, feature = "debug-dump"))]
#[derive(Debug)]
pub struct IR(pub(crate) Doc);
pub trait Emit {
fn emit(&self, doc: &mut Doc);
}
impl Emit for Doc {
fn emit(&self, doc: &mut Doc) {
doc.0.extend_from_slice(&self.0);
}
}
impl<T: Emit + ?Sized> Emit for &T {
fn emit(&self, doc: &mut Doc) {
(*self).emit(doc);
}
}
impl<T: Emit> Emit for Option<T> {
fn emit(&self, doc: &mut Doc) {
if let Some(x) = self {
x.emit(doc);
}
}
}
pub fn text_width(s: &str) -> usize {
s.chars().count()
}
impl Doc {
pub fn try_compact(&self, mut limit: Option<i32>) -> Option<Self> {
let mut result = Vec::new();
let mut stack: Vec<std::slice::Iter<'_, Elem>> = vec![self.iter()];
while let Some(iter) = stack.last_mut() {
let Some(elem) = iter.next() else {
stack.pop();
continue;
};
match elem {
Elem::Text(_, _, _, t) => {
if let Some(n) = limit.as_mut() {
#[allow(clippy::cast_possible_truncation, clippy::cast_possible_wrap)]
{
*n -= text_width(t) as i32;
}
}
result.push(elem.clone());
}
Elem::Spacing(Spacing::Hardspace | Spacing::Space | Spacing::Softspace) => {
if let Some(n) = limit.as_mut() {
*n -= 1;
}
result.push(Elem::Spacing(Spacing::Hardspace));
}
Elem::Spacing(Spacing::Break | Spacing::Softbreak) => {}
Elem::Spacing(_) => return None,
Elem::Nest(..) => result.push(elem.clone()),
Elem::Group(_, inner) => stack.push(inner.iter()),
}
if matches!(limit, Some(n) if n < 0) {
return None;
}
}
Some(Self(result))
}
}