use crate::cst::{Item, ListKind, Node};
const MAX_WIDTH: usize = 78;
fn leading_args(head: &str) -> usize {
match head {
"make-channel-introduction" => 1,
_ => 0,
}
}
fn delims(kind: &ListKind) -> (&str, &'static str) {
match kind {
ListKind::Paren => ("(", ")"),
ListKind::Bracket => ("[", "]"),
ListKind::Vector => ("#(", ")"),
ListKind::TaggedVector(s) => (s.as_str(), ")"),
}
}
fn prefix_core(prefix: &str) -> &str {
for cand in [
"#,@", "#$@", ",@", "#'", "#`", "#,", "#~", "#$", "#+", "'", "`", ",",
] {
if prefix.starts_with(cand) {
return cand;
}
}
prefix
}
fn has_comment(node: &Node) -> bool {
match node {
Node::List { items, .. } => items.iter().any(|i| match i {
Item::LineComment(_) | Item::BlockComment(_) | Item::DatumComment(_) => true,
Item::Node(n) => has_comment(n),
Item::Ws(_) => false,
}),
Node::Prefixed { inner, .. } => has_comment(inner),
_ => false,
}
}
fn flat_source(node: &Node) -> String {
match node {
Node::Atom(s) | Node::Str(s) => s.clone(),
Node::Prefixed { prefix, inner } => {
format!("{}{}", prefix_core(prefix), flat_source(inner))
}
Node::List { kind, items } => {
let parts: Vec<String> = items
.iter()
.filter_map(|i| match i {
Item::Node(n) => Some(flat_source(n)),
_ => None,
})
.collect();
let (open, close) = delims(kind);
format!("{open}{}{close}", parts.join(" "))
}
}
}
enum El<'a> {
Data(&'a Node),
Comment(&'a str),
}
fn break_list(kind: &ListKind, items: &[Item], indent: usize) -> String {
let (open, close) = delims(kind);
let els: Vec<El> = items
.iter()
.filter_map(|i| match i {
Item::Node(n) => Some(El::Data(n)),
Item::LineComment(s) | Item::BlockComment(s) | Item::DatumComment(s) => {
Some(El::Comment(s))
}
Item::Ws(_) => None,
})
.collect();
let mut out = String::from(open);
let mut rest = els.as_slice();
let mut lead = 0;
if let Some(El::Data(head)) = els.first() {
out.push_str(&pretty(head, indent + open.len()));
if let Node::Atom(a) = head {
lead = leading_args(a);
}
rest = &els[1..];
}
let child_indent = indent + 2;
let pad = format!("\n{}", " ".repeat(child_indent));
let mut args_seen = 0usize;
for el in rest {
out.push_str(&pad);
match el {
El::Comment(s) => out.push_str(s),
El::Data(n) => {
args_seen += 1;
if args_seen <= lead {
out.push_str(&flat_source(n));
} else {
out.push_str(&pretty(n, child_indent));
}
}
}
}
out.push_str(close);
out
}
pub fn pretty(node: &Node, indent: usize) -> String {
let flat = flat_source(node);
if !has_comment(node) && indent + flat.len() <= MAX_WIDTH {
return flat;
}
match node {
Node::List { kind, items } => break_list(kind, items, indent),
Node::Prefixed { prefix, inner } => {
let p = prefix_core(prefix);
format!("{p}{}", pretty(inner, indent + p.len()))
}
_ => flat,
}
}
impl Node {
pub fn to_pretty(&self, indent: usize) -> String {
pretty(self, indent)
}
}