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