use alloc::vec::Vec;
use core::fmt::Write;
use crate::doc::{Doc, Node};
#[derive(Clone, Copy, PartialEq, Eq)]
enum Mode {
Flat,
Break,
}
struct Frame<'a> {
indent: isize,
mode: Mode,
node: &'a Node,
}
pub(crate) fn layout<W: Write>(root: &Doc, width: usize, out: &mut W) -> core::fmt::Result {
let width = width as isize;
let mut col: isize = 0;
let mut stack: Vec<Frame<'_>> = Vec::with_capacity(16);
stack.push(Frame {
indent: 0,
mode: Mode::Break,
node: &root.0,
});
while let Some(Frame { indent, mode, node }) = stack.pop() {
match node {
Node::Nil => {}
Node::Text(s, w) => {
out.write_str(s)?;
col = col.saturating_add(*w as isize);
}
Node::Cat(a, b) => {
stack.push(Frame {
indent,
mode,
node: &b.0,
});
stack.push(Frame {
indent,
mode,
node: &a.0,
});
}
Node::Nest(j, x) => stack.push(Frame {
indent: indent.saturating_add(*j),
mode,
node: &x.0,
}),
Node::Line => match mode {
Mode::Flat => {
out.write_str(" ")?;
col = col.saturating_add(1);
}
Mode::Break => col = new_line(out, indent)?,
},
Node::SoftLine => match mode {
Mode::Flat => {}
Mode::Break => col = new_line(out, indent)?,
},
Node::HardLine => col = new_line(out, indent)?,
Node::Group(x) => {
let mode = if fits(width - col, indent, &x.0, &stack) {
Mode::Flat
} else {
Mode::Break
};
stack.push(Frame {
indent,
mode,
node: &x.0,
});
}
}
}
Ok(())
}
#[inline]
fn new_line<W: Write>(out: &mut W, indent: isize) -> Result<isize, core::fmt::Error> {
out.write_str("\n")?;
let indent = indent.max(0);
write_spaces(out, indent as usize)?;
Ok(indent)
}
#[inline]
fn write_spaces<W: Write>(out: &mut W, mut n: usize) -> core::fmt::Result {
const SPACES: &str = " ";
while n > 0 {
let take = n.min(SPACES.len());
out.write_str(&SPACES[..take])?;
n -= take;
}
Ok(())
}
fn fits(avail: isize, indent: isize, group: &Node, stack: &[Frame<'_>]) -> bool {
if avail < 0 {
return false;
}
let mut remaining = avail;
let mut local: Vec<(isize, Mode, &Node)> = Vec::new();
local.push((indent, Mode::Flat, group));
let mut cont = stack.len();
loop {
let (i, mode, node) = match local.pop() {
Some(item) => item,
None => {
if cont == 0 {
return true;
}
cont -= 1;
let frame = &stack[cont];
(frame.indent, frame.mode, frame.node)
}
};
match node {
Node::Nil => {}
Node::Text(_, w) => {
remaining -= *w as isize;
if remaining < 0 {
return false;
}
}
Node::Cat(a, b) => {
local.push((i, mode, &b.0));
local.push((i, mode, &a.0));
}
Node::Nest(j, x) => local.push((i.saturating_add(*j), mode, &x.0)),
Node::Line => match mode {
Mode::Flat => {
remaining -= 1;
if remaining < 0 {
return false;
}
}
Mode::Break => return true,
},
Node::SoftLine => {
if mode == Mode::Break {
return true;
}
}
Node::HardLine => match mode {
Mode::Flat => return false,
Mode::Break => return true,
},
Node::Group(x) => local.push((i, Mode::Flat, &x.0)),
}
}
}
#[cfg(feature = "std")]
pub(crate) fn layout_io<W: std::io::Write>(
root: &Doc,
width: usize,
out: &mut W,
) -> std::io::Result<()> {
struct Adapter<'w, W: std::io::Write> {
inner: &'w mut W,
err: Option<std::io::Error>,
}
impl<W: std::io::Write> Write for Adapter<'_, W> {
fn write_str(&mut self, s: &str) -> core::fmt::Result {
match self.inner.write_all(s.as_bytes()) {
Ok(()) => Ok(()),
Err(e) => {
self.err = Some(e);
Err(core::fmt::Error)
}
}
}
}
let mut adapter = Adapter {
inner: out,
err: None,
};
match layout(root, width, &mut adapter) {
Ok(()) => Ok(()),
Err(_) => Err(adapter
.err
.unwrap_or_else(|| std::io::Error::other("formatting error"))),
}
}