1use crate::canonical::canonicalize_file;
2use draxl_ast::{
3 BinaryOp, CommentNode, DocNode, Expr, File, Item, ItemFn, Literal, Meta, Param, Path, Pattern,
4 Stmt, Type, UnaryOp, UseTree, Variant,
5};
6
7pub fn print_file(file: &File) -> String {
9 let canonical = canonicalize_file(file);
10 let mut out = String::new();
11 write_item_list(&canonical.items, &mut out, 0);
12 out.push('\n');
13 out
14}
15
16fn write_item_list(items: &[Item], out: &mut String, indent: usize) {
17 for (index, item) in items.iter().enumerate() {
18 if index > 0 && needs_item_gap(items, index - 1, index) {
19 out.push('\n');
20 }
21 write_item(item, out, indent);
22 }
23}
24
25fn write_item(item: &Item, out: &mut String, indent: usize) {
26 match item {
27 Item::Mod(node) => {
28 indent_line(out, indent);
29 out.push_str(&format_meta_prefix(&node.meta));
30 out.push_str("mod ");
31 out.push_str(&node.name);
32 out.push_str(" {");
33 if node.items.is_empty() {
34 out.push_str("}\n");
35 return;
36 }
37 out.push('\n');
38 write_item_list(&node.items, out, indent + 1);
39 indent_line(out, indent);
40 out.push_str("}\n");
41 }
42 Item::Use(node) => {
43 indent_line(out, indent);
44 out.push_str(&format_meta_prefix(&node.meta));
45 out.push_str("use ");
46 out.push_str(&format_use_tree(&node.tree));
47 out.push_str(";\n");
48 }
49 Item::Struct(node) => {
50 indent_line(out, indent);
51 out.push_str(&format_meta_prefix(&node.meta));
52 out.push_str("struct ");
53 out.push_str(&node.name);
54 out.push_str(" {\n");
55 for field in &node.fields {
56 indent_line(out, indent + 1);
57 out.push_str(&format_meta_prefix(&field.meta));
58 out.push_str(&field.name);
59 out.push_str(": ");
60 out.push_str(&format_type(&field.ty));
61 out.push_str(",\n");
62 }
63 indent_line(out, indent);
64 out.push_str("}\n");
65 }
66 Item::Enum(node) => {
67 indent_line(out, indent);
68 out.push_str(&format_meta_prefix(&node.meta));
69 out.push_str("enum ");
70 out.push_str(&node.name);
71 out.push_str(" {\n");
72 for variant in &node.variants {
73 write_variant(variant, out, indent + 1);
74 }
75 indent_line(out, indent);
76 out.push_str("}\n");
77 }
78 Item::Fn(node) => write_fn(node, out, indent),
79 Item::Doc(node) => write_doc(node, out, indent),
80 Item::Comment(node) => write_comment(node, out, indent),
81 }
82}
83
84fn write_variant(variant: &Variant, out: &mut String, indent: usize) {
85 indent_line(out, indent);
86 out.push_str(&format_meta_prefix(&variant.meta));
87 out.push_str(&variant.name);
88 out.push_str(",\n");
89}
90
91fn write_fn(node: &ItemFn, out: &mut String, indent: usize) {
92 indent_line(out, indent);
93 out.push_str(&format_meta_prefix(&node.meta));
94 out.push_str("fn ");
95 out.push_str(&node.name);
96 out.push('(');
97 for (index, param) in node.params.iter().enumerate() {
98 if index > 0 {
99 out.push_str(", ");
100 }
101 out.push_str(&format_param(param));
102 }
103 out.push(')');
104 if let Some(ret_ty) = &node.ret_ty {
105 out.push_str(" -> ");
106 out.push_str(&format_type(ret_ty));
107 }
108 out.push_str(" {\n");
109 write_stmt_list(&node.body.stmts, out, indent + 1);
110 indent_line(out, indent);
111 out.push_str("}\n");
112}
113
114fn format_param(param: &Param) -> String {
115 let mut out = format_meta_prefix(¶m.meta);
116 out.push_str(¶m.name);
117 out.push_str(": ");
118 out.push_str(&format_type(¶m.ty));
119 out
120}
121
122fn write_stmt_list(stmts: &[Stmt], out: &mut String, indent: usize) {
123 for stmt in stmts {
124 write_stmt(stmt, out, indent);
125 }
126}
127
128fn write_stmt(stmt: &Stmt, out: &mut String, indent: usize) {
129 match stmt {
130 Stmt::Let(node) => {
131 indent_line(out, indent);
132 out.push_str(&format_meta_prefix(&node.meta));
133 out.push_str("let ");
134 out.push_str(&format_pattern(&node.pat));
135 out.push_str(" = ");
136 out.push_str(&format_expr(&node.value, indent));
137 out.push_str(";\n");
138 }
139 Stmt::Expr(node) => {
140 indent_line(out, indent);
141 out.push_str(&format_meta_prefix(&node.meta));
142 out.push_str(&format_expr(&node.expr, indent));
143 if node.has_semi {
144 out.push(';');
145 }
146 out.push('\n');
147 }
148 Stmt::Item(item) => write_item(item, out, indent),
149 Stmt::Doc(node) => write_doc(node, out, indent),
150 Stmt::Comment(node) => write_comment(node, out, indent),
151 }
152}
153
154fn write_doc(node: &DocNode, out: &mut String, indent: usize) {
155 indent_line(out, indent);
156 out.push_str(&format_meta_prefix(&node.meta));
157 out.push_str("///");
158 if !node.text.is_empty() {
159 out.push(' ');
160 out.push_str(&node.text);
161 }
162 out.push('\n');
163}
164
165fn write_comment(node: &CommentNode, out: &mut String, indent: usize) {
166 indent_line(out, indent);
167 out.push_str(&format_meta_prefix(&node.meta));
168 out.push_str("//");
169 if !node.text.is_empty() {
170 out.push(' ');
171 out.push_str(&node.text);
172 }
173 out.push('\n');
174}
175
176fn indent_line(out: &mut String, indent: usize) {
177 for _ in 0..indent {
178 out.push_str(" ");
179 }
180}
181
182fn format_meta_prefix(meta: &Meta) -> String {
183 let mut out = String::new();
184 out.push('@');
185 out.push_str(&meta.id);
186 if let Some(rank) = &meta.rank {
187 out.push('[');
188 out.push_str(rank);
189 out.push(']');
190 }
191 if let Some(anchor) = &meta.anchor {
192 out.push_str("->");
193 out.push_str(anchor);
194 }
195 out.push(' ');
196 out
197}
198
199fn format_type(ty: &Type) -> String {
200 match ty {
201 Type::Path(node) => {
202 let mut out = format_meta_prefix(&node.meta);
203 out.push_str(&format_path(&node.path));
204 out
205 }
206 }
207}
208
209fn format_pattern(pattern: &Pattern) -> String {
210 match pattern {
211 Pattern::Ident(node) => {
212 let mut out = String::new();
213 if let Some(meta) = &node.meta {
214 out.push_str(&format_meta_prefix(meta));
215 }
216 out.push_str(&node.name);
217 out
218 }
219 Pattern::Wild(node) => {
220 let mut out = String::new();
221 if let Some(meta) = &node.meta {
222 out.push_str(&format_meta_prefix(meta));
223 }
224 out.push('_');
225 out
226 }
227 }
228}
229
230fn format_expr(expr: &Expr, indent: usize) -> String {
231 format_expr_prec(expr, indent, 0)
232}
233
234fn format_expr_prec(expr: &Expr, indent: usize, outer_prec: u8) -> String {
235 match expr {
236 Expr::Path(node) => format_wrapped_expr(node.meta.as_ref(), format_path(&node.path), false),
237 Expr::Lit(node) => {
238 let core = match &node.value {
239 Literal::Int(value) => value.to_string(),
240 Literal::Str(value) => format!("\"{}\"", escape_string(value)),
241 };
242 format_wrapped_expr(node.meta.as_ref(), core, false)
243 }
244 Expr::Group(node) => {
245 let core = format!("({})", format_expr(&node.expr, indent));
246 format_wrapped_expr(node.meta.as_ref(), core, false)
247 }
248 Expr::Binary(node) => {
249 let prec = binary_precedence(node.op);
250 let core = format!(
251 "{} {} {}",
252 format_expr_prec(&node.lhs, indent, prec),
253 match node.op {
254 BinaryOp::Add => "+",
255 BinaryOp::Sub => "-",
256 BinaryOp::Lt => "<",
257 },
258 format_expr_prec(&node.rhs, indent, prec + 1)
259 );
260 format_wrapped_expr(node.meta.as_ref(), core, prec < outer_prec)
261 }
262 Expr::Unary(node) => {
263 let prec = 30;
264 let core = format!(
265 "{}{}",
266 match node.op {
267 UnaryOp::Neg => "-",
268 },
269 format_expr_prec(&node.expr, indent, prec)
270 );
271 format_wrapped_expr(node.meta.as_ref(), core, prec < outer_prec)
272 }
273 Expr::Call(node) => {
274 let prec = 40;
275 let mut core = format_expr_prec(&node.callee, indent, prec);
276 core.push('(');
277 for (index, arg) in node.args.iter().enumerate() {
278 if index > 0 {
279 core.push_str(", ");
280 }
281 core.push_str(&format_expr(arg, indent));
282 }
283 core.push(')');
284 format_wrapped_expr(node.meta.as_ref(), core, prec < outer_prec)
285 }
286 Expr::Match(node) => {
287 let mut core = String::new();
288 core.push_str("match ");
289 core.push_str(&format_expr(&node.scrutinee, indent));
290 core.push_str(" {\n");
291 for arm in &node.arms {
292 indent_line(&mut core, indent + 1);
293 core.push_str(&format_meta_prefix(&arm.meta));
294 core.push_str(&format_pattern(&arm.pat));
295 if let Some(guard) = &arm.guard {
296 core.push_str(" if ");
297 core.push_str(&format_expr(guard, indent + 1));
298 }
299 core.push_str(" => ");
300 core.push_str(&format_expr(&arm.body, indent + 1));
301 core.push_str(",\n");
302 }
303 indent_line(&mut core, indent);
304 core.push('}');
305 format_wrapped_expr(node.meta.as_ref(), core, false)
306 }
307 Expr::Block(block) => {
308 let mut core = String::new();
309 core.push_str("{\n");
310 write_stmt_list(&block.stmts, &mut core, indent + 1);
311 indent_line(&mut core, indent);
312 core.push('}');
313 format_wrapped_expr(block.meta.as_ref(), core, false)
314 }
315 }
316}
317
318fn format_wrapped_expr(meta: Option<&Meta>, mut core: String, wrap: bool) -> String {
319 if wrap {
320 core = format!("({core})");
321 }
322 if let Some(meta) = meta {
323 let mut out = format_meta_prefix(meta);
324 out.push_str(&core);
325 out
326 } else {
327 core
328 }
329}
330
331fn binary_precedence(op: BinaryOp) -> u8 {
332 match op {
333 BinaryOp::Lt => 10,
334 BinaryOp::Add | BinaryOp::Sub => 20,
335 }
336}
337
338fn needs_item_gap(items: &[Item], current: usize, next: usize) -> bool {
339 let current = &items[current];
340 let next = &items[next];
341 match current {
342 Item::Doc(_) | Item::Comment(_) => {
343 resolved_item_target(items, current) != resolved_item_target(items, next)
344 }
345 _ => true,
346 }
347}
348
349fn resolved_item_target<'a>(items: &'a [Item], item: &'a Item) -> Option<&'a str> {
350 if !matches!(item, Item::Doc(_) | Item::Comment(_)) {
351 return Some(item.meta().id.as_str());
352 }
353 if let Some(anchor) = item.meta().anchor.as_deref() {
354 return Some(anchor);
355 }
356 let index = items
357 .iter()
358 .position(|candidate| std::ptr::eq(candidate, item))?;
359 for candidate in &items[index + 1..] {
360 if !matches!(candidate, Item::Doc(_) | Item::Comment(_)) {
361 return Some(candidate.meta().id.as_str());
362 }
363 }
364 None
365}
366
367fn format_path(path: &Path) -> String {
368 path.segments.join("::")
369}
370
371fn format_use_tree(tree: &UseTree) -> String {
372 match tree {
373 UseTree::Name(node) => node.name.clone(),
374 UseTree::Path(node) => format!("{}::{}", node.prefix, format_use_tree(&node.tree)),
375 UseTree::Group(node) => {
376 let mut out = String::from("{");
377 for (index, item) in node.items.iter().enumerate() {
378 if index > 0 {
379 out.push_str(", ");
380 }
381 out.push_str(&format_use_tree(item));
382 }
383 out.push('}');
384 out
385 }
386 UseTree::Glob(_) => "*".to_owned(),
387 }
388}
389
390fn escape_string(value: &str) -> String {
391 let mut out = String::new();
392 for ch in value.chars() {
393 match ch {
394 '"' => out.push_str("\\\""),
395 '\\' => out.push_str("\\\\"),
396 '\n' => out.push_str("\\n"),
397 '\r' => out.push_str("\\r"),
398 '\t' => out.push_str("\\t"),
399 _ => out.push(ch),
400 }
401 }
402 out
403}