Skip to main content

draxl_rust/lower/
mod.rs

1//! Lowering validated Draxl Rust-profile input to ordinary Rust source.
2//!
3//! Lowering assumes the input already passed structural validation. The output
4//! strips Draxl metadata and preserves only the modeled Rust subset.
5
6use draxl_ast::{
7    canonicalize_file, BinaryOp, Block, CommentNode, DocNode, Expr, File, Item, ItemFn, Literal,
8    Path, Pattern, Stmt, Type, UnaryOp, UseTree,
9};
10
11/// Lowers a validated Draxl file to ordinary Rust.
12pub fn lower_file(file: &File) -> String {
13    let canonical = canonicalize_file(file);
14    let mut out = String::new();
15    write_item_list(&canonical.items, &mut out, 0);
16    out.push('\n');
17    out
18}
19
20fn write_item_list(items: &[Item], out: &mut String, indent: usize) {
21    for (index, item) in items.iter().enumerate() {
22        if index > 0 && needs_item_gap(items, index - 1, index) {
23            out.push('\n');
24        }
25        write_item(item, out, indent);
26    }
27}
28
29fn write_item(item: &Item, out: &mut String, indent: usize) {
30    match item {
31        Item::Mod(node) => {
32            indent_line(out, indent);
33            out.push_str("mod ");
34            out.push_str(&node.name);
35            out.push_str(" {");
36            if node.items.is_empty() {
37                out.push_str("}\n");
38                return;
39            }
40            out.push('\n');
41            write_item_list(&node.items, out, indent + 1);
42            indent_line(out, indent);
43            out.push_str("}\n");
44        }
45        Item::Use(node) => {
46            indent_line(out, indent);
47            out.push_str("use ");
48            out.push_str(&format_use_tree(&node.tree));
49            out.push_str(";\n");
50        }
51        Item::Struct(node) => {
52            indent_line(out, indent);
53            out.push_str("struct ");
54            out.push_str(&node.name);
55            out.push_str(" {\n");
56            for field in &node.fields {
57                indent_line(out, indent + 1);
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("enum ");
69            out.push_str(&node.name);
70            out.push_str(" {\n");
71            for variant in &node.variants {
72                indent_line(out, indent + 1);
73                out.push_str(&variant.name);
74                out.push_str(",\n");
75            }
76            indent_line(out, indent);
77            out.push_str("}\n");
78        }
79        Item::Fn(node) => write_fn(node, out, indent),
80        Item::Doc(node) => write_doc(node, out, indent),
81        Item::Comment(node) => write_comment(node, out, indent),
82    }
83}
84
85fn write_fn(node: &ItemFn, out: &mut String, indent: usize) {
86    indent_line(out, indent);
87    out.push_str("fn ");
88    out.push_str(&node.name);
89    out.push('(');
90    for (index, param) in node.params.iter().enumerate() {
91        if index > 0 {
92            out.push_str(", ");
93        }
94        out.push_str(&param.name);
95        out.push_str(": ");
96        out.push_str(&format_type(&param.ty));
97    }
98    out.push(')');
99    if let Some(ret_ty) = &node.ret_ty {
100        out.push_str(" -> ");
101        out.push_str(&format_type(ret_ty));
102    }
103    out.push_str(" {\n");
104    write_stmt_list(&node.body.stmts, out, indent + 1);
105    indent_line(out, indent);
106    out.push_str("}\n");
107}
108
109fn write_stmt_list(stmts: &[Stmt], out: &mut String, indent: usize) {
110    for stmt in stmts {
111        write_stmt(stmt, out, indent);
112    }
113}
114
115fn write_stmt(stmt: &Stmt, out: &mut String, indent: usize) {
116    match stmt {
117        Stmt::Let(node) => {
118            indent_line(out, indent);
119            out.push_str("let ");
120            out.push_str(&format_pattern(&node.pat));
121            out.push_str(" = ");
122            out.push_str(&format_expr(&node.value, indent));
123            out.push_str(";\n");
124        }
125        Stmt::Expr(node) => {
126            indent_line(out, indent);
127            out.push_str(&format_expr(&node.expr, indent));
128            if node.has_semi {
129                out.push(';');
130            }
131            out.push('\n');
132        }
133        Stmt::Item(item) => write_item(item, out, indent),
134        Stmt::Doc(node) => write_doc(node, out, indent),
135        Stmt::Comment(node) => write_comment(node, out, indent),
136    }
137}
138
139fn write_doc(node: &DocNode, out: &mut String, indent: usize) {
140    indent_line(out, indent);
141    out.push_str("///");
142    if !node.text.is_empty() {
143        out.push(' ');
144        out.push_str(&node.text);
145    }
146    out.push('\n');
147}
148
149fn write_comment(node: &CommentNode, out: &mut String, indent: usize) {
150    indent_line(out, indent);
151    out.push_str("//");
152    if !node.text.is_empty() {
153        out.push(' ');
154        out.push_str(&node.text);
155    }
156    out.push('\n');
157}
158
159fn format_pattern(pattern: &Pattern) -> String {
160    match pattern {
161        Pattern::Ident(node) => node.name.clone(),
162        Pattern::Wild(_) => "_".to_owned(),
163    }
164}
165
166fn format_type(ty: &Type) -> String {
167    match ty {
168        Type::Path(node) => format_path(&node.path),
169    }
170}
171
172fn format_expr(expr: &Expr, indent: usize) -> String {
173    match expr {
174        Expr::Path(node) => format_path(&node.path),
175        Expr::Lit(node) => match &node.value {
176            Literal::Int(value) => value.to_string(),
177            Literal::Str(value) => format!("\"{}\"", escape_string(value)),
178        },
179        Expr::Group(node) => format!("({})", format_expr(&node.expr, indent)),
180        Expr::Binary(node) => format!(
181            "{} {} {}",
182            format_expr(&node.lhs, indent),
183            match node.op {
184                BinaryOp::Add => "+",
185                BinaryOp::Sub => "-",
186                BinaryOp::Lt => "<",
187            },
188            format_expr(&node.rhs, indent)
189        ),
190        Expr::Unary(node) => format!(
191            "{}{}",
192            match node.op {
193                UnaryOp::Neg => "-",
194            },
195            format_expr(&node.expr, indent)
196        ),
197        Expr::Call(node) => {
198            let mut out = format_expr(&node.callee, indent);
199            out.push('(');
200            for (index, arg) in node.args.iter().enumerate() {
201                if index > 0 {
202                    out.push_str(", ");
203                }
204                out.push_str(&format_expr(arg, indent));
205            }
206            out.push(')');
207            out
208        }
209        Expr::Match(node) => {
210            let mut out = String::new();
211            out.push_str("match ");
212            out.push_str(&format_expr(&node.scrutinee, indent));
213            out.push_str(" {\n");
214            for arm in &node.arms {
215                indent_line(&mut out, indent + 1);
216                out.push_str(&format_pattern(&arm.pat));
217                if let Some(guard) = &arm.guard {
218                    out.push_str(" if ");
219                    out.push_str(&format_expr(guard, indent + 1));
220                }
221                out.push_str(" => ");
222                out.push_str(&format_expr(&arm.body, indent + 1));
223                out.push_str(",\n");
224            }
225            indent_line(&mut out, indent);
226            out.push('}');
227            out
228        }
229        Expr::Block(Block { stmts, .. }) => {
230            let mut out = String::new();
231            out.push_str("{\n");
232            write_stmt_list(stmts, &mut out, indent + 1);
233            indent_line(&mut out, indent);
234            out.push('}');
235            out
236        }
237    }
238}
239
240fn needs_item_gap(items: &[Item], current: usize, next: usize) -> bool {
241    let current = &items[current];
242    let next = &items[next];
243    match current {
244        Item::Doc(_) | Item::Comment(_) => {
245            resolved_item_target(items, current) != resolved_item_target(items, next)
246        }
247        _ => true,
248    }
249}
250
251fn resolved_item_target<'a>(items: &'a [Item], item: &'a Item) -> Option<&'a str> {
252    if !matches!(item, Item::Doc(_) | Item::Comment(_)) {
253        return Some(item.meta().id.as_str());
254    }
255    if let Some(anchor) = item.meta().anchor.as_deref() {
256        return Some(anchor);
257    }
258    let index = items
259        .iter()
260        .position(|candidate| std::ptr::eq(candidate, item))?;
261    for candidate in &items[index + 1..] {
262        if !matches!(candidate, Item::Doc(_) | Item::Comment(_)) {
263            return Some(candidate.meta().id.as_str());
264        }
265    }
266    None
267}
268
269fn indent_line(out: &mut String, indent: usize) {
270    for _ in 0..indent {
271        out.push_str("  ");
272    }
273}
274
275fn format_path(path: &Path) -> String {
276    path.segments.join("::")
277}
278
279fn format_use_tree(tree: &UseTree) -> String {
280    match tree {
281        UseTree::Name(node) => node.name.clone(),
282        UseTree::Path(node) => format!("{}::{}", node.prefix, format_use_tree(&node.tree)),
283        UseTree::Group(node) => {
284            let mut out = String::from("{");
285            for (index, item) in node.items.iter().enumerate() {
286                if index > 0 {
287                    out.push_str(", ");
288                }
289                out.push_str(&format_use_tree(item));
290            }
291            out.push('}');
292            out
293        }
294        UseTree::Glob(_) => "*".to_owned(),
295    }
296}
297
298fn escape_string(value: &str) -> String {
299    let mut out = String::new();
300    for ch in value.chars() {
301        match ch {
302            '"' => out.push_str("\\\""),
303            '\\' => out.push_str("\\\\"),
304            '\n' => out.push_str("\\n"),
305            '\r' => out.push_str("\\r"),
306            '\t' => out.push_str("\\t"),
307            _ => out.push(ch),
308        }
309    }
310    out
311}