Skip to main content

doge_compiler/ast/
dump.rs

1use super::{Expr, InterpPart, Params, Script, Stmt};
2
3/// Render a script as an indented tree (two spaces per level). Stable and
4/// language-agnostic — this is what `doge check` prints on success.
5pub fn dump(script: &Script) -> String {
6    let mut out = String::new();
7    out.push_str("Script\n");
8    for stmt in &script.stmts {
9        dump_stmt(stmt, 1, &mut out);
10    }
11    out
12}
13
14fn indent(level: usize, out: &mut String) {
15    for _ in 0..level {
16        out.push_str("  ");
17    }
18}
19
20fn line(level: usize, text: &str, out: &mut String) {
21    indent(level, out);
22    out.push_str(text);
23    out.push('\n');
24}
25
26/// Render a binding-target name list for a destructuring `Decl`/`For` header:
27/// the leading names comma-joined, then a trailing `many rest` collector when one
28/// is present, e.g. `a, b` or `a, many rest`.
29fn name_list(names: &[String], rest: Option<&str>) -> String {
30    let mut parts: Vec<String> = names.to_vec();
31    if let Some(rest) = rest {
32        parts.push(format!("many {rest}"));
33    }
34    parts.join(", ")
35}
36
37fn dump_block(label: &str, body: &[Stmt], level: usize, out: &mut String) {
38    line(level, label, out);
39    for stmt in body {
40        dump_stmt(stmt, level + 1, out);
41    }
42}
43
44fn dump_stmt(stmt: &Stmt, level: usize, out: &mut String) {
45    match stmt {
46        Stmt::Decl {
47            names, rest, expr, ..
48        } => {
49            line(
50                level,
51                &format!("Decl {}", name_list(names, rest.as_deref())),
52                out,
53            );
54            dump_expr(expr, level + 1, out);
55        }
56        Stmt::ConstDecl { name, expr, .. } => {
57            line(level, &format!("ConstDecl {name}"), out);
58            dump_expr(expr, level + 1, out);
59        }
60        Stmt::Import { module, .. } => line(level, &format!("Import {module}"), out),
61        Stmt::Assign {
62            targets,
63            rest,
64            expr,
65            flavored,
66            ..
67        } => {
68            let label = if *flavored { "Assign very" } else { "Assign" };
69            line(level, label, out);
70            for target in targets {
71                dump_block_expr("target", target, level + 1, out);
72            }
73            if let Some(rest) = rest {
74                dump_block_expr("many target", rest, level + 1, out);
75            }
76            dump_block_expr("value", expr, level + 1, out);
77        }
78        Stmt::Bark { expr, .. } => {
79            line(level, "Bark", out);
80            dump_expr(expr, level + 1, out);
81        }
82        Stmt::If {
83            branches,
84            else_body,
85            ..
86        } => {
87            line(level, "If", out);
88            for (cond, body) in branches {
89                line(level + 1, "branch", out);
90                dump_block_expr("cond", cond, level + 2, out);
91                dump_block("body", body, level + 2, out);
92            }
93            if let Some(body) = else_body {
94                dump_block("else", body, level + 1, out);
95            }
96        }
97        Stmt::For {
98            vars,
99            rest,
100            iter,
101            body,
102            ..
103        } => {
104            line(
105                level,
106                &format!("For {}", name_list(vars, rest.as_deref())),
107                out,
108            );
109            dump_block_expr("in", iter, level + 1, out);
110            dump_block("body", body, level + 1, out);
111        }
112        Stmt::While { cond, body, .. } => {
113            line(level, "While", out);
114            dump_block_expr("cond", cond, level + 1, out);
115            dump_block("body", body, level + 1, out);
116        }
117        Stmt::FuncDef {
118            name, params, body, ..
119        } => {
120            let params = if params.is_empty() {
121                String::new()
122            } else {
123                format!(" much {}", dump_params(params))
124            };
125            dump_block(&format!("FuncDef {name}{params}"), body, level, out);
126        }
127        Stmt::ObjDef {
128            name,
129            parent,
130            methods,
131            ..
132        } => {
133            let heading = match parent {
134                Some(parent) => format!("ObjDef {name} much {parent}"),
135                None => format!("ObjDef {name}"),
136            };
137            dump_block(&heading, methods, level, out);
138        }
139        Stmt::Try {
140            body,
141            err_name,
142            handler,
143            ..
144        } => {
145            line(level, "Try", out);
146            dump_block("body", body, level + 1, out);
147            dump_block(&format!("catch {err_name}"), handler, level + 1, out);
148        }
149        Stmt::Return { expr, .. } => match expr {
150            Some(expr) => {
151                line(level, "Return", out);
152                dump_expr(expr, level + 1, out);
153            }
154            None => line(level, "Return", out),
155        },
156        Stmt::Bonk { expr, .. } => {
157            line(level, "Bonk", out);
158            dump_expr(expr, level + 1, out);
159        }
160        Stmt::Amaze { cond, message, .. } => {
161            line(level, "Amaze", out);
162            dump_expr(cond, level + 1, out);
163            if let Some(message) = message {
164                dump_expr(message, level + 1, out);
165            }
166        }
167        Stmt::Bork { .. } => line(level, "Bork", out),
168        Stmt::Continue { .. } => line(level, "Continue", out),
169        Stmt::ExprStmt { expr } => {
170            line(level, "ExprStmt", out);
171            dump_expr(expr, level + 1, out);
172        }
173    }
174}
175
176/// Render a parameter list for the `much …` header line: `name`, `mood = …` for
177/// a defaulted parameter, `many rest` for the variadic.
178fn dump_params(params: &Params) -> String {
179    let mut parts: Vec<String> = params
180        .params
181        .iter()
182        .map(|p| {
183            if p.default.is_some() {
184                format!("{} = …", p.name)
185            } else {
186                p.name.clone()
187            }
188        })
189        .collect();
190    if let Some(rest) = &params.vararg {
191        parts.push(format!("many {rest}"));
192    }
193    parts.join(", ")
194}
195
196/// Dump an expression under a named sub-heading, e.g. `cond` / `target`.
197fn dump_block_expr(label: &str, expr: &Expr, level: usize, out: &mut String) {
198    line(level, label, out);
199    dump_expr(expr, level + 1, out);
200}
201
202fn dump_expr(expr: &Expr, level: usize, out: &mut String) {
203    match expr {
204        Expr::Int { value, .. } => line(level, &format!("Int {value}"), out),
205        Expr::Float { value, .. } => line(level, &format!("Float {value}"), out),
206        Expr::Str { value, .. } => line(level, &format!("Str {value:?}"), out),
207        Expr::Bool { value, .. } => line(level, &format!("Bool {value}"), out),
208        Expr::None { .. } => line(level, "None", out),
209        Expr::Ident { name, .. } => line(level, &format!("Ident {name}"), out),
210        Expr::List { items, .. } => {
211            line(level, "List", out);
212            for item in items {
213                dump_expr(item, level + 1, out);
214            }
215        }
216        Expr::Dict { entries, .. } => {
217            line(level, "Dict", out);
218            for (key, value) in entries {
219                line(level + 1, "entry", out);
220                dump_block_expr("key", key, level + 2, out);
221                dump_block_expr("value", value, level + 2, out);
222            }
223        }
224        Expr::Binary { op, lhs, rhs, .. } => {
225            line(level, &format!("Binary {}", op.symbol()), out);
226            dump_expr(lhs, level + 1, out);
227            dump_expr(rhs, level + 1, out);
228        }
229        Expr::Unary { op, operand, .. } => {
230            line(level, &format!("Unary {}", op.symbol()), out);
231            dump_expr(operand, level + 1, out);
232        }
233        Expr::Call {
234            callee,
235            args,
236            kwargs,
237            ..
238        } => {
239            line(level, "Call", out);
240            dump_block_expr("callee", callee, level + 1, out);
241            for arg in args {
242                dump_block_expr("arg", arg, level + 1, out);
243            }
244            for (name, value) in kwargs {
245                dump_block_expr(&format!("kwarg {name}"), value, level + 1, out);
246            }
247        }
248        Expr::Index { obj, index, .. } => {
249            line(level, "Index", out);
250            dump_block_expr("obj", obj, level + 1, out);
251            dump_block_expr("index", index, level + 1, out);
252        }
253        Expr::Slice {
254            obj,
255            start,
256            end,
257            step,
258            ..
259        } => {
260            line(level, "Slice", out);
261            dump_block_expr("obj", obj, level + 1, out);
262            for (label, part) in [("start", start), ("end", end), ("step", step)] {
263                match part {
264                    Some(expr) => dump_block_expr(label, expr, level + 1, out),
265                    None => line(level + 1, &format!("{label} none"), out),
266                }
267            }
268        }
269        Expr::Ternary {
270            cond,
271            then,
272            otherwise,
273            ..
274        } => {
275            line(level, "Ternary", out);
276            dump_block_expr("cond", cond, level + 1, out);
277            dump_block_expr("then", then, level + 1, out);
278            dump_block_expr("else", otherwise, level + 1, out);
279        }
280        Expr::Attr { obj, name, .. } => {
281            line(level, &format!("Attr {name}"), out);
282            dump_expr(obj, level + 1, out);
283        }
284        Expr::SuperCall { method, args, .. } => {
285            line(level, &format!("SuperCall {method}"), out);
286            for arg in args {
287                dump_block_expr("arg", arg, level + 1, out);
288            }
289        }
290        Expr::StrInterp { parts, .. } => {
291            line(level, "StrInterp", out);
292            for part in parts {
293                match part {
294                    InterpPart::Lit(text) => line(level + 1, &format!("Lit {text:?}"), out),
295                    InterpPart::Expr(expr) => dump_block_expr("hole", expr, level + 1, out),
296                }
297            }
298        }
299    }
300}