Skip to main content

draxl_printer/
render.rs

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
7/// Prints a file into canonical Draxl source.
8pub 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(&param.meta);
116    out.push_str(&param.name);
117    out.push_str(": ");
118    out.push_str(&format_type(&param.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}