Skip to main content

draxl_rust/render/
mod.rs

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