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