1use crate::cst::{Item, ListKind, Node};
2
3const MAX_WIDTH: usize = 78;
4
5fn leading_args(head: &str) -> usize {
9 match head {
10 "make-channel-introduction" => 1,
11 _ => 0,
12 }
13}
14
15fn delims(kind: &ListKind) -> (&str, &'static str) {
16 match kind {
17 ListKind::Paren => ("(", ")"),
18 ListKind::Bracket => ("[", "]"),
19 ListKind::Vector => ("#(", ")"),
20 ListKind::TaggedVector(s) => (s.as_str(), ")"),
21 }
22}
23
24fn prefix_core(prefix: &str) -> &str {
26 for cand in [
27 "#,@", "#$@", ",@", "#'", "#`", "#,", "#~", "#$", "#+", "'", "`", ",",
28 ] {
29 if prefix.starts_with(cand) {
30 return cand;
31 }
32 }
33 prefix
34}
35
36fn has_comment(node: &Node) -> bool {
37 match node {
38 Node::List { items, .. } => items.iter().any(|i| match i {
39 Item::LineComment(_) | Item::BlockComment(_) | Item::DatumComment(_) => true,
40 Item::Node(n) => has_comment(n),
41 Item::Ws(_) => false,
42 }),
43 Node::Prefixed { inner, .. } => has_comment(inner),
44 _ => false,
45 }
46}
47
48fn flat_source(node: &Node) -> String {
50 match node {
51 Node::Atom(s) | Node::Str(s) => s.clone(),
52 Node::Prefixed { prefix, inner } => {
53 format!("{}{}", prefix_core(prefix), flat_source(inner))
54 }
55 Node::List { kind, items } => {
56 let parts: Vec<String> = items
57 .iter()
58 .filter_map(|i| match i {
59 Item::Node(n) => Some(flat_source(n)),
60 _ => None,
61 })
62 .collect();
63 let (open, close) = delims(kind);
64 format!("{open}{}{close}", parts.join(" "))
65 }
66 }
67}
68
69enum El<'a> {
70 Data(&'a Node),
71 Comment(&'a str),
72}
73
74fn break_list(kind: &ListKind, items: &[Item], indent: usize) -> String {
75 let (open, close) = delims(kind);
76 let els: Vec<El> = items
77 .iter()
78 .filter_map(|i| match i {
79 Item::Node(n) => Some(El::Data(n)),
80 Item::LineComment(s) | Item::BlockComment(s) | Item::DatumComment(s) => {
81 Some(El::Comment(s))
82 }
83 Item::Ws(_) => None,
84 })
85 .collect();
86
87 let mut out = String::from(open);
88 let mut rest = els.as_slice();
89 let mut lead = 0;
90 if let Some(El::Data(head)) = els.first() {
91 out.push_str(&pretty(head, indent + open.len()));
92 if let Node::Atom(a) = head {
93 lead = leading_args(a);
94 }
95 rest = &els[1..];
96 }
97
98 let child_indent = indent + 2;
99 let pad = format!("\n{}", " ".repeat(child_indent));
100 let mut args_seen = 0usize;
101 for el in rest {
102 out.push_str(&pad);
103 match el {
104 El::Comment(s) => out.push_str(s),
105 El::Data(n) => {
106 args_seen += 1;
107 if args_seen <= lead {
108 out.push_str(&flat_source(n));
109 } else {
110 out.push_str(&pretty(n, child_indent));
111 }
112 }
113 }
114 }
115 out.push_str(close);
116 out
117}
118
119pub fn pretty(node: &Node, indent: usize) -> String {
121 let flat = flat_source(node);
122 if !has_comment(node) && indent + flat.len() <= MAX_WIDTH {
123 return flat;
124 }
125 match node {
126 Node::List { kind, items } => break_list(kind, items, indent),
127 Node::Prefixed { prefix, inner } => {
128 let p = prefix_core(prefix);
129 format!("{p}{}", pretty(inner, indent + p.len()))
130 }
131 _ => flat,
133 }
134}
135
136impl Node {
137 pub fn to_pretty(&self, indent: usize) -> String {
138 pretty(self, indent)
139 }
140}