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