1use draxl_ast::{
7 canonicalize_file, BinaryOp, Block, CommentNode, DocNode, Expr, File, Item, ItemFn, Literal,
8 Path, Pattern, Stmt, Type, UnaryOp, UseTree,
9};
10
11pub 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(¶m.name);
95 out.push_str(": ");
96 out.push_str(&format_type(¶m.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}