Skip to main content

lex_syntax/
printer.rs

1//! Pretty-printer for the syntax tree. Designed so
2//! `parse(text) → print → parse` round-trips on the canonical AST.
3
4use crate::syntax::*;
5use std::fmt::Write;
6
7pub fn print_program(program: &Program) -> String {
8    let mut p = Printer::new();
9    p.program(program);
10    p.out
11}
12
13struct Printer {
14    out: String,
15    indent: usize,
16}
17
18impl Printer {
19    fn new() -> Self { Self { out: String::new(), indent: 0 } }
20
21    fn write_indent(&mut self) {
22        for _ in 0..self.indent {
23            self.out.push_str("  ");
24        }
25    }
26
27    fn nl(&mut self) {
28        self.out.push('\n');
29    }
30
31    fn program(&mut self, p: &Program) {
32        // Top-of-file comments live above any item. Always emitted at
33        // column 0 — agents and humans both expect module-level
34        // documentation to start at the left margin (`lex fmt`
35        // canonical form).
36        for c in &p.leading_comments {
37            writeln!(self.out, "{c}").unwrap();
38        }
39        if !p.leading_comments.is_empty() && !p.items.is_empty() {
40            self.nl();
41        }
42        for (i, item) in p.items.iter().enumerate() {
43            if i > 0 { self.nl(); }
44            self.item(item);
45        }
46        if !p.items.is_empty() {
47            self.nl();
48        }
49        // Trailing comments at the end of file (after the last item).
50        // Most code doesn't have these; preserved for round-trip
51        // fidelity on the rare file that does.
52        if !p.trailing_comments.is_empty() {
53            if !p.items.is_empty() { self.nl(); }
54            for c in &p.trailing_comments {
55                writeln!(self.out, "{c}").unwrap();
56            }
57        }
58    }
59
60    fn item(&mut self, item: &Item) {
61        // Emit any leading-comment block attached to the item before
62        // the item itself. Each entry is a raw source line including
63        // the `#` prefix; written verbatim, one per line.
64        let leading: &[String] = match item {
65            Item::Import(i) => &i.leading_comments,
66            Item::TypeDecl(td) => &td.leading_comments,
67            Item::FnDecl(fd) => &fd.leading_comments,
68        };
69        for c in leading {
70            writeln!(self.out, "{c}").unwrap();
71        }
72        match item {
73            Item::Import(i) => {
74                writeln!(self.out, "import \"{}\" as {}", i.reference, i.alias).unwrap();
75            }
76            Item::TypeDecl(td) => self.type_decl(td),
77            Item::FnDecl(fd) => self.fn_decl(fd),
78        }
79    }
80
81    fn type_decl(&mut self, td: &TypeDecl) {
82        write!(self.out, "type {}", td.name).unwrap();
83        if !td.params.is_empty() {
84            write!(self.out, "[{}]", td.params.join(", ")).unwrap();
85        }
86        write!(self.out, " = ").unwrap();
87        self.type_expr(&td.definition);
88        self.nl();
89    }
90
91    fn fn_decl(&mut self, fd: &FnDecl) {
92        write!(self.out, "fn {}", fd.name).unwrap();
93        if !fd.type_params.is_empty() {
94            write!(self.out, "[{}]", fd.type_params.join(", ")).unwrap();
95        }
96        write!(self.out, "(").unwrap();
97        for (i, p) in fd.params.iter().enumerate() {
98            if i > 0 { write!(self.out, ", ").unwrap(); }
99            write!(self.out, "{} :: ", p.name).unwrap();
100            self.type_expr(&p.ty);
101        }
102        write!(self.out, ") -> ").unwrap();
103        self.effects(&fd.effects);
104        self.type_expr(&fd.return_type);
105        if fd.examples.is_empty() {
106            write!(self.out, " ").unwrap();
107        } else {
108            self.examples(&fd.name, &fd.examples);
109            self.nl();
110        }
111        self.block(&fd.body);
112        self.nl();
113    }
114
115    fn examples(&mut self, fn_name: &str, examples: &[Example]) {
116        if examples.is_empty() { return; }
117        self.nl();
118        write!(self.out, "  examples {{").unwrap();
119        self.indent += 2;
120        for (i, ex) in examples.iter().enumerate() {
121            self.nl();
122            self.write_indent();
123            write!(self.out, "{}(", fn_name).unwrap();
124            for (j, a) in ex.args.iter().enumerate() {
125                if j > 0 { write!(self.out, ", ").unwrap(); }
126                self.expr(a);
127            }
128            write!(self.out, ") => ").unwrap();
129            self.expr(&ex.expected);
130            if i + 1 < examples.len() {
131                write!(self.out, ",").unwrap();
132            }
133        }
134        self.indent -= 2;
135        self.nl();
136        write!(self.out, "  }}").unwrap();
137    }
138
139    fn effects(&mut self, effects: &[Effect]) {
140        if effects.is_empty() { return; }
141        write!(self.out, "[").unwrap();
142        for (i, e) in effects.iter().enumerate() {
143            if i > 0 { write!(self.out, ", ").unwrap(); }
144            write!(self.out, "{}", e.name).unwrap();
145            if let Some(arg) = &e.arg {
146                match arg {
147                    EffectArg::Str(s) => write!(self.out, "(\"{}\")", s).unwrap(),
148                    EffectArg::Int(n) => write!(self.out, "({})", n).unwrap(),
149                    EffectArg::Ident(s) => write!(self.out, "({})", s).unwrap(),
150                }
151            }
152        }
153        write!(self.out, "] ").unwrap();
154    }
155
156    fn type_expr(&mut self, t: &TypeExpr) {
157        match t {
158            TypeExpr::Named { name, args } => {
159                write!(self.out, "{}", name).unwrap();
160                if !args.is_empty() {
161                    write!(self.out, "[").unwrap();
162                    for (i, a) in args.iter().enumerate() {
163                        if i > 0 { write!(self.out, ", ").unwrap(); }
164                        self.type_expr(a);
165                    }
166                    write!(self.out, "]").unwrap();
167                }
168            }
169            TypeExpr::Record(fs) => {
170                write!(self.out, "{{ ").unwrap();
171                for (i, f) in fs.iter().enumerate() {
172                    if i > 0 { write!(self.out, ", ").unwrap(); }
173                    write!(self.out, "{} :: ", f.name).unwrap();
174                    self.type_expr(&f.ty);
175                }
176                write!(self.out, " }}").unwrap();
177            }
178            TypeExpr::RecordWithSpreads { spreads, fields } => {
179                write!(self.out, "{{ ").unwrap();
180                for (i, s) in spreads.iter().enumerate() {
181                    if i > 0 { write!(self.out, ", ").unwrap(); }
182                    write!(self.out, "...{}", s).unwrap();
183                }
184                for f in fields {
185                    write!(self.out, ", {} :: ", f.name).unwrap();
186                    self.type_expr(&f.ty);
187                }
188                write!(self.out, " }}").unwrap();
189            }
190            TypeExpr::Tuple(items) => {
191                write!(self.out, "(").unwrap();
192                for (i, it) in items.iter().enumerate() {
193                    if i > 0 { write!(self.out, ", ").unwrap(); }
194                    self.type_expr(it);
195                }
196                write!(self.out, ")").unwrap();
197            }
198            TypeExpr::Function { params, effects, ret } => {
199                write!(self.out, "(").unwrap();
200                for (i, p) in params.iter().enumerate() {
201                    if i > 0 { write!(self.out, ", ").unwrap(); }
202                    self.type_expr(p);
203                }
204                write!(self.out, ") -> ").unwrap();
205                self.effects(effects);
206                self.type_expr(ret);
207            }
208            TypeExpr::Union(variants) => {
209                for (i, v) in variants.iter().enumerate() {
210                    if i > 0 { write!(self.out, " | ").unwrap(); }
211                    write!(self.out, "{}", v.name).unwrap();
212                    if let Some(payload) = &v.payload {
213                        write!(self.out, "(").unwrap();
214                        self.type_expr(payload);
215                        write!(self.out, ")").unwrap();
216                    }
217                }
218            }
219            TypeExpr::Refined { base, binding, predicate } => {
220                self.type_expr(base);
221                write!(self.out, "{{{} | ", binding).unwrap();
222                self.expr(predicate);
223                write!(self.out, "}}").unwrap();
224            }
225        }
226    }
227
228    fn block(&mut self, b: &Block) {
229        write!(self.out, "{{").unwrap();
230        self.indent += 1;
231        for stmt in &b.statements {
232            self.nl();
233            self.write_indent();
234            self.statement(stmt);
235        }
236        self.nl();
237        self.write_indent();
238        self.expr(&b.result);
239        self.indent -= 1;
240        self.nl();
241        self.write_indent();
242        write!(self.out, "}}").unwrap();
243    }
244
245    fn statement(&mut self, s: &Statement) {
246        match s {
247            Statement::Let { name, ty, value } => {
248                write!(self.out, "let {}", name).unwrap();
249                if let Some(ty) = ty {
250                    write!(self.out, " :: ").unwrap();
251                    self.type_expr(ty);
252                }
253                write!(self.out, " := ").unwrap();
254                self.expr(value);
255            }
256            Statement::Expr(e) => self.expr(e),
257        }
258    }
259
260    fn expr(&mut self, e: &Expr) {
261        self.expr_prec(e, 0);
262    }
263
264    fn expr_prec(&mut self, e: &Expr, parent_prec: u8) {
265        match e {
266            Expr::Lit(l) => self.literal(l),
267            Expr::Var(n) => { write!(self.out, "{}", n).unwrap(); }
268            Expr::Block(b) => self.block(b),
269            Expr::Call { callee, args } => {
270                self.expr_prec(callee, 100);
271                write!(self.out, "(").unwrap();
272                for (i, a) in args.iter().enumerate() {
273                    if i > 0 { write!(self.out, ", ").unwrap(); }
274                    self.expr(a);
275                }
276                write!(self.out, ")").unwrap();
277            }
278            Expr::Pipe { left, right } => {
279                if parent_prec > 0 { write!(self.out, "(").unwrap(); }
280                self.expr_prec(left, 1);
281                write!(self.out, " |> ").unwrap();
282                self.expr_prec(right, 1);
283                if parent_prec > 0 { write!(self.out, ")").unwrap(); }
284            }
285            Expr::Try(inner) => {
286                self.expr_prec(inner, 100);
287                write!(self.out, "?").unwrap();
288            }
289            Expr::Field { value, field } => {
290                self.expr_prec(value, 100);
291                write!(self.out, ".{}", field).unwrap();
292            }
293            Expr::BinOp { op, lhs, rhs } => {
294                let prec = op.precedence() + 10;
295                if parent_prec > prec { write!(self.out, "(").unwrap(); }
296                self.expr_prec(lhs, prec);
297                write!(self.out, " {} ", op.as_str()).unwrap();
298                self.expr_prec(rhs, prec + 1);
299                if parent_prec > prec { write!(self.out, ")").unwrap(); }
300            }
301            Expr::UnaryOp { op, expr } => {
302                let s = match op { UnaryOp::Neg => "-", UnaryOp::Not => "not " };
303                write!(self.out, "{}", s).unwrap();
304                self.expr_prec(expr, 100);
305            }
306            Expr::If { cond, then_block, else_block } => {
307                write!(self.out, "if ").unwrap();
308                self.expr(cond);
309                write!(self.out, " ").unwrap();
310                self.block(then_block);
311                write!(self.out, " else ").unwrap();
312                self.block(else_block);
313            }
314            Expr::Match { scrutinee, arms } => {
315                write!(self.out, "match ").unwrap();
316                self.expr(scrutinee);
317                write!(self.out, " {{").unwrap();
318                self.indent += 1;
319                for arm in arms {
320                    self.nl();
321                    self.write_indent();
322                    self.pattern(&arm.pattern);
323                    write!(self.out, " => ").unwrap();
324                    self.expr(&arm.body);
325                    write!(self.out, ",").unwrap();
326                }
327                self.indent -= 1;
328                self.nl();
329                self.write_indent();
330                write!(self.out, "}}").unwrap();
331            }
332            Expr::RecordLit(fields) => {
333                write!(self.out, "{{ ").unwrap();
334                for (i, f) in fields.iter().enumerate() {
335                    if i > 0 { write!(self.out, ", ").unwrap(); }
336                    write!(self.out, "{}: ", f.name).unwrap();
337                    self.expr(&f.value);
338                }
339                write!(self.out, " }}").unwrap();
340            }
341            Expr::TupleLit(items) => {
342                write!(self.out, "(").unwrap();
343                for (i, it) in items.iter().enumerate() {
344                    if i > 0 { write!(self.out, ", ").unwrap(); }
345                    self.expr(it);
346                }
347                write!(self.out, ")").unwrap();
348            }
349            Expr::ListLit(items) => {
350                write!(self.out, "[").unwrap();
351                for (i, it) in items.iter().enumerate() {
352                    if i > 0 { write!(self.out, ", ").unwrap(); }
353                    self.expr(it);
354                }
355                write!(self.out, "]").unwrap();
356            }
357            Expr::Constructor { name, args } => {
358                write!(self.out, "{}", name).unwrap();
359                if !args.is_empty() {
360                    write!(self.out, "(").unwrap();
361                    for (i, a) in args.iter().enumerate() {
362                        if i > 0 { write!(self.out, ", ").unwrap(); }
363                        self.expr(a);
364                    }
365                    write!(self.out, ")").unwrap();
366                }
367            }
368            Expr::Ascription { value, ty } => {
369                write!(self.out, "(").unwrap();
370                self.expr(value);
371                write!(self.out, " :: ").unwrap();
372                self.type_expr(ty);
373                write!(self.out, ")").unwrap();
374            }
375            Expr::Lambda(l) => {
376                write!(self.out, "fn (").unwrap();
377                for (i, p) in l.params.iter().enumerate() {
378                    if i > 0 { write!(self.out, ", ").unwrap(); }
379                    write!(self.out, "{} :: ", p.name).unwrap();
380                    self.type_expr(&p.ty);
381                }
382                write!(self.out, ") -> ").unwrap();
383                self.effects(&l.effects);
384                self.type_expr(&l.return_type);
385                write!(self.out, " ").unwrap();
386                self.block(&l.body);
387            }
388        }
389    }
390
391    fn literal(&mut self, l: &Literal) {
392        match l {
393            Literal::Int(n) => write!(self.out, "{}", n).unwrap(),
394            Literal::Float(n) => write!(self.out, "{}", format_float(*n)).unwrap(),
395            Literal::Str(s) => write!(self.out, "\"{}\"", escape(s)).unwrap(),
396            Literal::Bytes(b) => {
397                write!(self.out, "b\"").unwrap();
398                for &c in b {
399                    if c.is_ascii() && (c as char).is_ascii_graphic() && c != b'"' && c != b'\\' {
400                        self.out.push(c as char);
401                    } else {
402                        write!(self.out, "\\x{:02x}", c).unwrap();
403                    }
404                }
405                write!(self.out, "\"").unwrap();
406            }
407            Literal::Bool(b) => write!(self.out, "{}", b).unwrap(),
408            Literal::Unit => write!(self.out, "()").unwrap(),
409        }
410    }
411
412    fn pattern(&mut self, p: &Pattern) {
413        match p {
414            Pattern::Lit(l) => self.literal(l),
415            Pattern::Var(n) => { write!(self.out, "{}", n).unwrap(); }
416            Pattern::Wild => { write!(self.out, "_").unwrap(); }
417            Pattern::Constructor { name, args } => {
418                write!(self.out, "{}", name).unwrap();
419                if !args.is_empty() {
420                    write!(self.out, "(").unwrap();
421                    for (i, a) in args.iter().enumerate() {
422                        if i > 0 { write!(self.out, ", ").unwrap(); }
423                        self.pattern(a);
424                    }
425                    write!(self.out, ")").unwrap();
426                }
427            }
428            Pattern::Record { fields, rest: _ } => {
429                write!(self.out, "{{ ").unwrap();
430                for (i, f) in fields.iter().enumerate() {
431                    if i > 0 { write!(self.out, ", ").unwrap(); }
432                    write!(self.out, "{}", f.name).unwrap();
433                    if let Some(p) = &f.pattern {
434                        write!(self.out, ": ").unwrap();
435                        self.pattern(p);
436                    }
437                }
438                write!(self.out, " }}").unwrap();
439            }
440            Pattern::Tuple(items) => {
441                write!(self.out, "(").unwrap();
442                for (i, it) in items.iter().enumerate() {
443                    if i > 0 { write!(self.out, ", ").unwrap(); }
444                    self.pattern(it);
445                }
446                write!(self.out, ")").unwrap();
447            }
448        }
449    }
450}
451
452fn escape(s: &str) -> String {
453    let mut out = String::with_capacity(s.len());
454    for c in s.chars() {
455        match c {
456            '\\' => out.push_str("\\\\"),
457            '"' => out.push_str("\\\""),
458            '\n' => out.push_str("\\n"),
459            '\t' => out.push_str("\\t"),
460            '\r' => out.push_str("\\r"),
461            c => out.push(c),
462        }
463    }
464    out
465}
466
467fn format_float(n: f64) -> String {
468    if n.is_finite() && n == n.trunc() {
469        format!("{:.1}", n)
470    } else {
471        format!("{}", n)
472    }
473}