use alloc::string::String;
use alloc::vec::Vec;
use core::mem;
use pretty_lang::Doc;
use crate::flat::{Ev, Flat, NodeInfo, Sig, is_blank};
use crate::rules::Indent;
use crate::style::{NodeStyle, Sp, Style, join_opt};
struct Out {
doc: Option<Doc>,
run: String,
acc: u32,
}
impl Out {
fn new(acc: u32) -> Self {
Self {
doc: None,
run: String::new(),
acc,
}
}
#[inline]
fn text(&mut self, s: &str) {
self.run.push_str(s);
}
fn flush(&mut self) {
if !self.run.is_empty() {
let text = Doc::text(mem::take(&mut self.run));
self.attach(text);
}
}
fn doc(&mut self, d: Doc) {
self.flush();
self.attach(d);
}
#[inline]
fn attach(&mut self, d: Doc) {
self.doc = Some(match self.doc.take() {
Some(prev) => prev.append(d),
None => d,
});
}
fn is_empty(&self) -> bool {
self.doc.is_none() && self.run.is_empty()
}
fn newline_at_column_zero(&mut self, br: &mut Breaks) {
let d = br.column_zero(self.acc);
self.doc(d);
}
fn raw(&mut self, text: &str, br: &mut Breaks) {
let mut lines = text.split('\n');
if let Some(first) = lines.next() {
self.text(first);
}
for line in lines {
self.newline_at_column_zero(br);
self.text(line);
}
}
fn finish(mut self) -> Doc {
self.flush();
self.doc.unwrap_or_default()
}
}
struct Breaks {
line: Doc,
softline: Doc,
hardline: Doc,
column_zero: Vec<Option<Doc>>,
}
const CACHED_COLUMNS: usize = 256;
impl Breaks {
fn new() -> Self {
Self {
line: Doc::line(),
softline: Doc::softline(),
hardline: Doc::hardline(),
column_zero: Vec::new(),
}
}
fn column_zero(&mut self, acc: u32) -> Doc {
if acc == 0 {
return self.hardline.clone();
}
let i = acc as usize;
if i >= CACHED_COLUMNS {
return self.hardline.clone().nest(-cols(acc));
}
if self.column_zero.len() <= i {
self.column_zero.resize(i + 1, None);
}
match self.column_zero.get_mut(i) {
Some(Some(d)) => d.clone(),
Some(slot) => {
let d = self.hardline.clone().nest(-cols(acc));
*slot = Some(d.clone());
d
}
None => self.hardline.clone().nest(-cols(acc)),
}
}
}
#[inline]
fn cols(n: u32) -> isize {
isize::try_from(n).unwrap_or(isize::MAX)
}
struct Frame {
node: Option<u32>,
owns: bool,
nest: u32,
body: u8,
}
struct Prev<'a> {
start: u32,
end: u32,
text: &'a str,
after: Option<Sp>,
is_sep: bool,
}
#[derive(Clone, Copy)]
struct Next<'a> {
start: u32,
end: u32,
text: &'a str,
before: Option<Sp>,
}
struct GapCtx<'a, K> {
next: Option<Next<'a>>,
lca: Option<&'a NodeStyle<K>>,
prev_direct: bool,
opens: bool,
closes: bool,
kept: bool,
insert: bool,
}
#[derive(Default)]
struct Gap {
comments: Vec<(u32, u32)>,
ws: Vec<(u32, u32, u32)>,
}
pub(crate) struct Walker<'a, K> {
style: &'a Style<K>,
src: &'a str,
nodes: &'a [NodeInfo],
outs: Vec<Out>,
frames: Vec<Frame>,
pending: Vec<u32>,
exited: Vec<u32>,
trivia: Vec<(u32, u32)>,
prev: Option<Prev<'a>>,
gap: Gap,
keep: Vec<(u32, u32)>,
br: Breaks,
exact_end: bool,
cr: bool,
}
fn merge_spans(spans: &[syntax_lang::Span]) -> Vec<(u32, u32)> {
let mut v: Vec<(u32, u32)> = spans
.iter()
.map(|s| (s.start().to_u32(), s.end().to_u32()))
.collect();
v.sort_unstable();
let mut merged: Vec<(u32, u32)> = Vec::with_capacity(v.len());
for (start, end) in v {
match merged.last_mut() {
Some(last) if start <= last.1 => last.1 = last.1.max(end),
_ => merged.push((start, end)),
}
}
merged
}
impl<'a, K: Ord> Walker<'a, K> {
pub(crate) fn new(
style: &'a Style<K>,
src: &'a str,
flat: &'a Flat<'a, K>,
keep: &[syntax_lang::Span],
) -> Self {
Self {
style,
src,
nodes: &flat.nodes,
outs: alloc::vec![Out::new(0)],
frames: alloc::vec![Frame {
node: None,
owns: true,
nest: 0,
body: 0,
}],
pending: Vec::new(),
exited: Vec::new(),
trivia: Vec::new(),
prev: None,
gap: Gap::default(),
keep: merge_spans(keep),
br: Breaks::new(),
exact_end: false,
cr: false,
}
}
fn list_kept(&self, info: &NodeInfo) -> bool {
match (info.open_at, info.close_end) {
(Some(open), Some(close)) => self.kept(open, close, open, close),
_ => false,
}
}
fn kept(&self, start: u32, end: u32, gap_start: u32, gap_end: u32) -> bool {
if self.keep.is_empty() {
return false;
}
let i = self.keep.partition_point(|k| k.1 < start);
self.keep
.get(i..)
.unwrap_or(&[])
.iter()
.take_while(|k| k.0 <= end)
.any(|k| {
if k.0 == k.1 {
gap_start <= k.0 && k.0 <= gap_end
} else {
k.0 < end && start < k.1
}
})
}
pub(crate) fn run(mut self, events: &'a [Ev<'a, K>], bom: bool) -> Doc {
if bom {
self.top().text("\u{feff}");
}
for ev in events {
match *ev {
Ev::Enter(id) => self.pending.push(id),
Ev::Exit => self.exit(),
Ev::Trivia(start, end) => self.trivia.push((start, end)),
Ev::Sig(sig) => self.sig(sig),
}
}
while self.frames.len() > 1 || !self.pending.is_empty() {
self.exit();
}
self.write_gap(None);
let content = self.outs.iter().any(|o| !o.is_empty());
if self.style.final_newline && content && !self.exact_end {
if mem::take(&mut self.cr) {
self.top().text("\r");
}
let (out, br) = self.top_and_breaks();
out.doc(br.hardline.clone());
}
let mut doc = Doc::nil();
while let Some(out) = self.outs.pop() {
doc = out.finish().append(doc);
}
doc
}
#[inline]
fn top(&mut self) -> &mut Out {
self.top_and_breaks().0
}
#[inline]
fn top_and_breaks(&mut self) -> (&mut Out, &mut Breaks) {
if self.outs.is_empty() {
self.outs.push(Out::new(0));
}
let last = self.outs.len() - 1;
(&mut self.outs[last], &mut self.br)
}
fn raw(&mut self, text: &str) {
let (out, br) = self.top_and_breaks();
out.raw(text, br);
}
#[inline]
fn info(&self, node: Option<u32>) -> Option<&'a NodeInfo> {
node.and_then(|i| self.nodes.get(i as usize))
}
#[inline]
fn rule_of(&self, node: Option<u32>) -> Option<&'a NodeStyle<K>> {
self.style.node(self.info(node).and_then(|i| i.rule))
}
fn exit(&mut self) {
if self.pending.pop().is_some() {
return;
}
if self.frames.len() <= 1 {
return;
}
self.close_body();
let Some(frame) = self.frames.pop() else {
return;
};
if frame.owns {
if let Some(out) = self.outs.pop() {
let rule = self.rule_of(frame.node);
let mut d = out.finish();
if rule.is_some_and(|r| r.indent == Indent::Hanging) && frame.nest > 0 {
d = d.nest(cols(frame.nest));
}
if rule.is_some_and(|r| r.group) {
d = d.group();
}
self.top().doc(d);
}
}
if let Some(node) = frame.node {
self.exited.push(node);
}
}
fn close_body(&mut self) {
let Some(frame) = self.frames.last_mut() else {
return;
};
if frame.body != 1 {
return;
}
frame.body = 2;
let nest = frame.nest;
if let Some(body) = self.outs.pop() {
let d = body.finish().nest(cols(nest));
self.top().doc(d);
}
}
fn open_pending(&mut self) {
for i in 0..self.pending.len() {
let Some(&id) = self.pending.get(i) else {
break;
};
let info = self.info(Some(id));
let rule = self.style.node(info.and_then(|i| i.rule));
let base = self.top().acc;
let parent_rule = self
.frames
.last()
.and_then(|f| self.info(f.node))
.and_then(|i| i.rule);
let chained =
info.and_then(|i| i.rule).is_some() && parent_rule == info.and_then(|i| i.rule);
let nest = match rule.map(|r| r.indent) {
Some(Indent::Hanging) if chained => 0,
Some(Indent::Block | Indent::Hanging) => self
.style
.indent
.min(self.style.max_indent.saturating_sub(base)),
_ => 0,
};
let owns = rule.is_some_and(NodeStyle::owns_doc);
if owns {
let hanging = rule.is_some_and(|r| r.indent == Indent::Hanging);
self.outs.push(Out::new(if hanging {
base.saturating_add(nest)
} else {
base
}));
}
self.frames.push(Frame {
node: Some(id),
owns,
nest,
body: 0,
});
}
self.pending.clear();
}
fn sig(&mut self, sig: Sig<'a, K>) {
let parent_node = match self.pending.last() {
Some(&id) => Some(id),
None => self.frames.last().and_then(|f| f.node),
};
let parent_info = self.info(parent_node);
if self.pending.is_empty()
&& parent_info.is_some_and(|i| i.drop_sep_at == Some(sig.start) && !self.list_kept(i))
{
return;
}
let parent = self.style.node(parent_info.and_then(|i| i.rule));
let own = self.style.node(sig.rule);
let text = self
.src
.get(sig.start as usize..sig.end as usize)
.unwrap_or("");
let before = join_opt(
self.style.token_before(parent, sig.kind),
own.and_then(|r| r.before),
);
self.write_gap(Some(Next {
start: sig.start,
end: sig.end,
text,
before,
}));
self.open_pending();
self.unit(text);
let info = self.info(self.frames.last().and_then(|f| f.node));
let opens_body = info.is_some_and(|i| i.open_at == Some(sig.start))
&& parent.is_some_and(|r| r.indent == Indent::Block);
if let Some(frame) = self.frames.last_mut() {
if opens_body && frame.body == 0 && frame.owns {
frame.body = 1;
let acc = self
.outs
.last()
.map_or(0, |o| o.acc)
.saturating_add(frame.nest);
self.outs.push(Out::new(acc));
}
}
let after = join_opt(
self.style.token_after(parent, sig.kind),
own.and_then(|r| r.after),
);
let is_sep = parent
.and_then(|r| r.sep.as_ref())
.is_some_and(|s| s.kind == *sig.kind);
self.prev = Some(Prev {
start: sig.start,
end: sig.end,
text,
after,
is_sep,
});
self.trivia.clear();
self.exited.clear();
}
fn split_trivia(&mut self) {
let gap = &mut self.gap;
gap.comments.clear();
gap.ws.clear();
let mut ws: Option<(u32, u32, u32)> = None;
for &(start, end) in &self.trivia {
let text = self.src.get(start as usize..end as usize).unwrap_or("");
if is_blank(text, start) {
let lines = count_newlines(text);
ws = Some(match ws {
Some((s, _, n)) => (s, end, n.saturating_add(lines)),
None => (start, end, lines),
});
} else {
let w = ws.take().unwrap_or((start, start, 0));
gap.ws.push(w);
gap.comments.push((start, end));
if text.ends_with('\n') {
ws = Some((end, end, 1));
}
}
}
let end = self.gap.comments.last().map_or(0, |c| c.1);
self.gap.ws.push(ws.unwrap_or((end, end, 0)));
}
fn blank_lines(&self, lines: u32, lca: Option<&NodeStyle<K>>) -> u32 {
let limit = lca
.and_then(|r| r.blank_lines)
.unwrap_or(self.style.blank_lines);
lines.saturating_sub(1).min(u32::from(limit))
}
fn unit(&mut self, text: &str) {
self.raw(text);
self.cr = text.ends_with('\r');
}
fn space(&mut self) {
self.cr = false;
self.top().text(" ");
}
fn emit_space(&mut self, sp: Sp, blank: u32) {
let cr = mem::take(&mut self.cr);
let sp = if cr && sp.brk >= 1 { Sp::HARD } else { sp };
let (out, br) = self.top_and_breaks();
match sp.brk {
0 if sp.space => out.text(" "),
0 => {}
1 if sp.space => out.doc(br.line.clone()),
1 => out.doc(br.softline.clone()),
_ => {
if cr {
out.text("\r");
}
for _ in 0..blank {
out.newline_at_column_zero(br);
}
out.doc(br.hardline.clone());
}
}
}
fn emit_comment(&mut self, (start, end): (u32, u32)) {
let text = self.src.get(start as usize..end as usize).unwrap_or("");
let supplied = (end as usize) < self.src.len() || self.style.final_newline;
match text.strip_suffix('\n') {
Some(stripped) if supplied => {
self.raw(stripped);
self.cr = false;
}
_ => self.unit(text),
}
}
fn emit_kept_whitespace(&mut self, (start, end, lines): (u32, u32, u32)) {
let text = self.src.get(start as usize..end as usize).unwrap_or("");
let cr = mem::take(&mut self.cr);
let (out, br) = self.top_and_breaks();
if lines == 0 {
out.text(text);
return;
}
if cr {
out.text("\r");
}
for _ in 0..lines {
out.newline_at_column_zero(br);
}
let indent = text.rsplit('\n').next().unwrap_or("");
out.text(indent);
}
fn write_exact(&mut self, closes: bool) {
self.cr = false;
for i in 0..self.trivia.len() {
let (start, end) = self.trivia[i];
let text = self.src.get(start as usize..end as usize).unwrap_or("");
self.raw(text);
}
if closes {
self.close_body();
}
}
fn write_gap(&mut self, next: Option<Next<'a>>) {
self.split_trivia();
let ctx = self.gap_context(next);
if ctx.kept {
self.write_exact(ctx.closes);
if ctx.next.is_none() {
self.exact_end = true;
}
return;
}
let spacing = self.resolve(&ctx);
let (left, touching) = self.insert_separator(&ctx);
match spacing {
None => self.write_preserved(&ctx),
Some(sp) => self.write_formatted(&ctx, sp, left, touching),
}
}
fn gap_context(&self, next: Option<Next<'a>>) -> GapCtx<'a, K> {
let lca_node = self.frames.last().and_then(|f| f.node);
let info = self.info(lca_node);
let prev_direct = self.prev.is_some() && self.exited.is_empty();
let opens = prev_direct
&& self
.prev
.as_ref()
.is_some_and(|p| info.is_some_and(|i| i.open_at == Some(p.start)));
let closes = self.pending.is_empty()
&& next.is_some_and(|n| info.is_some_and(|i| i.close_at == Some(n.start)));
let first = self.trivia.first().map(|t| t.0);
let last = self.trivia.last().map(|t| t.1);
let (start, gap_start) = match &self.prev {
Some(p) => (Some(p.start), Some(p.end)),
None => (first, first),
};
let (end, gap_end) = match &next {
Some(n) => (Some(n.end), Some(n.start)),
None => (last, last),
};
let kept = match (start, end, gap_start, gap_end) {
(Some(s), Some(e), Some(gs), Some(ge)) => self.kept(s, e, gs, ge),
_ => false,
};
let insert = !kept && closes && info.is_some_and(|i| i.insert_sep && !self.list_kept(i));
GapCtx {
next,
lca: self.rule_of(lca_node),
prev_direct,
opens,
closes,
kept,
insert,
}
}
fn resolve(&self, ctx: &GapCtx<'a, K>) -> Option<Sp> {
let (Some(prev), Some(next)) = (&self.prev, &ctx.next) else {
return Some(Sp::NONE);
};
if ctx.opens && ctx.closes {
return ctx.lca.map(|r| r.empty);
}
let trailing = ctx.closes && (ctx.insert || (prev.is_sep && ctx.prev_direct));
let mut acc = next.before;
if !trailing {
acc = join_opt(acc, prev.after);
for &id in &self.exited {
acc = join_opt(acc, self.rule_of(Some(id)).and_then(|r| r.after));
}
}
if ctx.opens || ctx.closes {
let inner = ctx.lca.and_then(|r| r.delims.as_ref()).map(|d| d.inner);
acc = join_opt(acc, inner);
}
for &id in &self.pending {
acc = join_opt(acc, self.rule_of(Some(id)).and_then(|r| r.before));
}
acc
}
fn insert_separator(&mut self, ctx: &GapCtx<'a, K>) -> (Option<&'a str>, bool) {
let touching = matches!((&self.prev, &ctx.next), (Some(p), Some(n)) if p.end == n.start);
let left = self.prev.as_ref().map(|p| p.text);
if !ctx.insert {
return (left, touching);
}
let (Some(sep), Some(prev)) = (ctx.lca.and_then(|r| r.sep.as_ref()), &self.prev) else {
return (left, touching);
};
let mut sp = sep.before;
if let Some(after) = prev.after {
sp = sp.join(after);
}
for &id in &self.exited {
if let Some(after) = self.rule_of(Some(id)).and_then(|r| r.after) {
sp = sp.join(after);
}
}
if !sp.space && !sp.is_hard() && !(self.style.touch)(prev.text, &sep.text) {
sp.space = true;
}
self.emit_space(sp, 0);
let text: &'a str = &sep.text;
self.unit(text);
(Some(text), false)
}
fn write_preserved(&mut self, ctx: &GapCtx<'a, K>) {
let n = self.gap.comments.len();
for i in 0..n {
let w = self.gap.ws[i];
self.emit_kept_whitespace(w);
let c = self.gap.comments[i];
self.emit_comment(c);
}
let w = self.gap.ws[n];
self.emit_kept_whitespace(w);
if ctx.closes {
self.close_body();
}
}
fn write_formatted(
&mut self,
ctx: &GapCtx<'a, K>,
spacing: Sp,
left: Option<&'a str>,
touching: bool,
) {
let n = self.gap.comments.len();
let trailing = if self.prev.is_some() {
self.gap.ws.iter().take(n).take_while(|w| w.2 == 0).count()
} else {
0
};
for i in 0..trailing {
self.space();
let c = self.gap.comments[i];
self.emit_comment(c);
}
let last_ws = self.gap.ws[n];
if trailing < n {
self.write_leading(ctx, spacing, trailing);
return;
}
if ctx.closes {
self.close_body();
}
let (Some(next), Some(_)) = (&ctx.next, &self.prev) else {
return;
};
if trailing > 0 {
if last_ws.2 > 0 {
let blank = self.blank_lines(last_ws.2, ctx.lca);
self.emit_space(Sp::HARD, blank);
} else {
let kept = if last_ws.0 == last_ws.1 {
Sp::NONE
} else {
Sp::SINGLE
};
self.emit_space(spacing.join(kept), 0);
}
return;
}
let mut sp = spacing;
if !sp.space && !sp.is_hard() && !touching {
if let Some(l) = left {
if !(self.style.touch)(l, next.text) {
sp.space = true;
}
}
}
let blank = if sp.is_hard() {
self.blank_lines(last_ws.2, ctx.lca)
} else {
0
};
self.emit_space(sp, blank);
}
fn write_leading(&mut self, ctx: &GapCtx<'a, K>, spacing: Sp, from: usize) {
let n = self.gap.comments.len();
for j in from..n {
let w = self.gap.ws[j];
if j > from || self.prev.is_some() {
if w.2 > 0 {
let blank = self.blank_lines(w.2, ctx.lca);
self.emit_space(Sp::HARD, blank);
} else {
self.space();
}
}
let c = self.gap.comments[j];
self.emit_comment(c);
}
if ctx.closes {
self.close_body();
}
if ctx.next.is_none() {
return;
}
let last_ws = self.gap.ws[n];
if last_ws.2 > 0 {
let blank = self.blank_lines(last_ws.2, ctx.lca);
self.emit_space(Sp::HARD, blank);
} else if spacing.is_hard() {
self.emit_space(Sp::HARD, 0);
} else {
self.emit_space(Sp::SINGLE, 0);
}
}
}
#[inline]
fn count_newlines(text: &str) -> u32 {
let n = text.bytes().filter(|&b| b == b'\n').count();
u32::try_from(n).unwrap_or(u32::MAX)
}