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, fd.effect_row_var.as_ref());
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], tail: Option<&String>) {
140        if effects.is_empty() && tail.is_none() { 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        if let Some(v) = tail {
154            if effects.is_empty() {
155                write!(self.out, "| {}", v).unwrap();
156            } else {
157                write!(self.out, " | {}", v).unwrap();
158            }
159        }
160        write!(self.out, "] ").unwrap();
161    }
162
163    fn type_expr(&mut self, t: &TypeExpr) {
164        match t {
165            TypeExpr::Named { name, args } => {
166                write!(self.out, "{}", name).unwrap();
167                if !args.is_empty() {
168                    write!(self.out, "[").unwrap();
169                    for (i, a) in args.iter().enumerate() {
170                        if i > 0 { write!(self.out, ", ").unwrap(); }
171                        self.type_expr(a);
172                    }
173                    write!(self.out, "]").unwrap();
174                }
175            }
176            TypeExpr::Record(fs) => {
177                write!(self.out, "{{ ").unwrap();
178                for (i, f) in fs.iter().enumerate() {
179                    if i > 0 { write!(self.out, ", ").unwrap(); }
180                    write!(self.out, "{} :: ", f.name).unwrap();
181                    self.type_expr(&f.ty);
182                }
183                write!(self.out, " }}").unwrap();
184            }
185            TypeExpr::RecordWithSpreads { spreads, fields } => {
186                write!(self.out, "{{ ").unwrap();
187                for (i, s) in spreads.iter().enumerate() {
188                    if i > 0 { write!(self.out, ", ").unwrap(); }
189                    write!(self.out, "...{}", s).unwrap();
190                }
191                for f in fields {
192                    write!(self.out, ", {} :: ", f.name).unwrap();
193                    self.type_expr(&f.ty);
194                }
195                write!(self.out, " }}").unwrap();
196            }
197            TypeExpr::Tuple(items) => {
198                write!(self.out, "(").unwrap();
199                for (i, it) in items.iter().enumerate() {
200                    if i > 0 { write!(self.out, ", ").unwrap(); }
201                    self.type_expr(it);
202                }
203                write!(self.out, ")").unwrap();
204            }
205            TypeExpr::Function { params, effects, effect_row_var, ret } => {
206                write!(self.out, "(").unwrap();
207                for (i, p) in params.iter().enumerate() {
208                    if i > 0 { write!(self.out, ", ").unwrap(); }
209                    self.type_expr(p);
210                }
211                write!(self.out, ") -> ").unwrap();
212                self.effects(effects, effect_row_var.as_ref());
213                self.type_expr(ret);
214            }
215            TypeExpr::Union(variants) => {
216                for (i, v) in variants.iter().enumerate() {
217                    if i > 0 { write!(self.out, " | ").unwrap(); }
218                    write!(self.out, "{}", v.name).unwrap();
219                    if let Some(payload) = &v.payload {
220                        write!(self.out, "(").unwrap();
221                        self.type_expr(payload);
222                        write!(self.out, ")").unwrap();
223                    }
224                }
225            }
226            TypeExpr::Refined { base, binding, predicate } => {
227                self.type_expr(base);
228                write!(self.out, "{{{} | ", binding).unwrap();
229                self.expr(predicate);
230                write!(self.out, "}}").unwrap();
231            }
232        }
233    }
234
235    fn block(&mut self, b: &Block) {
236        write!(self.out, "{{").unwrap();
237        self.indent += 1;
238        for stmt in &b.statements {
239            self.nl();
240            self.write_indent();
241            self.statement(stmt);
242        }
243        self.nl();
244        self.write_indent();
245        self.expr(&b.result);
246        self.indent -= 1;
247        self.nl();
248        self.write_indent();
249        write!(self.out, "}}").unwrap();
250    }
251
252    fn statement(&mut self, s: &Statement) {
253        match s {
254            Statement::Let { name, ty, value } => {
255                write!(self.out, "let {}", name).unwrap();
256                if let Some(ty) = ty {
257                    write!(self.out, " :: ").unwrap();
258                    self.type_expr(ty);
259                }
260                write!(self.out, " := ").unwrap();
261                self.expr(value);
262            }
263            Statement::Expr(e) => self.expr(e),
264        }
265    }
266
267    fn expr(&mut self, e: &Expr) {
268        self.expr_prec(e, 0);
269    }
270
271    fn expr_prec(&mut self, e: &Expr, parent_prec: u8) {
272        match e {
273            Expr::Lit(l) => self.literal(l),
274            Expr::Var(n) => { write!(self.out, "{}", n).unwrap(); }
275            Expr::Block(b) => self.block(b),
276            Expr::Call { callee, args } => {
277                self.expr_prec(callee, 100);
278                write!(self.out, "(").unwrap();
279                for (i, a) in args.iter().enumerate() {
280                    if i > 0 { write!(self.out, ", ").unwrap(); }
281                    self.expr(a);
282                }
283                write!(self.out, ")").unwrap();
284            }
285            Expr::Pipe { left, right } => {
286                if parent_prec > 0 { write!(self.out, "(").unwrap(); }
287                self.expr_prec(left, 1);
288                write!(self.out, " |> ").unwrap();
289                self.expr_prec(right, 1);
290                if parent_prec > 0 { write!(self.out, ")").unwrap(); }
291            }
292            Expr::Try(inner) => {
293                self.expr_prec(inner, 100);
294                write!(self.out, "?").unwrap();
295            }
296            Expr::Field { value, field } => {
297                self.expr_prec(value, 100);
298                write!(self.out, ".{}", field).unwrap();
299            }
300            Expr::BinOp { op, lhs, rhs } => {
301                let prec = op.precedence() + 10;
302                if parent_prec > prec { write!(self.out, "(").unwrap(); }
303                self.expr_prec(lhs, prec);
304                write!(self.out, " {} ", op.as_str()).unwrap();
305                self.expr_prec(rhs, prec + 1);
306                if parent_prec > prec { write!(self.out, ")").unwrap(); }
307            }
308            Expr::UnaryOp { op, expr } => {
309                let s = match op { UnaryOp::Neg => "-", UnaryOp::Not => "not " };
310                write!(self.out, "{}", s).unwrap();
311                self.expr_prec(expr, 100);
312            }
313            Expr::If { cond, then_block, else_block } => {
314                write!(self.out, "if ").unwrap();
315                self.expr(cond);
316                write!(self.out, " ").unwrap();
317                self.block(then_block);
318                write!(self.out, " else ").unwrap();
319                self.block(else_block);
320            }
321            Expr::Match { scrutinee, arms } => {
322                write!(self.out, "match ").unwrap();
323                self.expr(scrutinee);
324                write!(self.out, " {{").unwrap();
325                self.indent += 1;
326                for arm in arms {
327                    self.nl();
328                    self.write_indent();
329                    self.pattern(&arm.pattern);
330                    write!(self.out, " => ").unwrap();
331                    self.expr(&arm.body);
332                    write!(self.out, ",").unwrap();
333                }
334                self.indent -= 1;
335                self.nl();
336                self.write_indent();
337                write!(self.out, "}}").unwrap();
338            }
339            Expr::RecordLit(fields) => {
340                write!(self.out, "{{ ").unwrap();
341                for (i, f) in fields.iter().enumerate() {
342                    if i > 0 { write!(self.out, ", ").unwrap(); }
343                    write!(self.out, "{}: ", f.name).unwrap();
344                    self.expr(&f.value);
345                }
346                write!(self.out, " }}").unwrap();
347            }
348            Expr::TupleLit(items) => {
349                write!(self.out, "(").unwrap();
350                for (i, it) in items.iter().enumerate() {
351                    if i > 0 { write!(self.out, ", ").unwrap(); }
352                    self.expr(it);
353                }
354                write!(self.out, ")").unwrap();
355            }
356            Expr::ListLit(items) => {
357                write!(self.out, "[").unwrap();
358                for (i, it) in items.iter().enumerate() {
359                    if i > 0 { write!(self.out, ", ").unwrap(); }
360                    self.expr(it);
361                }
362                write!(self.out, "]").unwrap();
363            }
364            Expr::Constructor { name, args } => {
365                write!(self.out, "{}", name).unwrap();
366                if !args.is_empty() {
367                    write!(self.out, "(").unwrap();
368                    for (i, a) in args.iter().enumerate() {
369                        if i > 0 { write!(self.out, ", ").unwrap(); }
370                        self.expr(a);
371                    }
372                    write!(self.out, ")").unwrap();
373                }
374            }
375            Expr::Ascription { value, ty } => {
376                write!(self.out, "(").unwrap();
377                self.expr(value);
378                write!(self.out, " :: ").unwrap();
379                self.type_expr(ty);
380                write!(self.out, ")").unwrap();
381            }
382            Expr::Lambda(l) => {
383                write!(self.out, "fn (").unwrap();
384                for (i, p) in l.params.iter().enumerate() {
385                    if i > 0 { write!(self.out, ", ").unwrap(); }
386                    write!(self.out, "{} :: ", p.name).unwrap();
387                    self.type_expr(&p.ty);
388                }
389                write!(self.out, ") -> ").unwrap();
390                self.effects(&l.effects, l.effect_row_var.as_ref());
391                self.type_expr(&l.return_type);
392                write!(self.out, " ").unwrap();
393                self.block(&l.body);
394            }
395        }
396    }
397
398    fn literal(&mut self, l: &Literal) {
399        match l {
400            Literal::Int(n) => write!(self.out, "{}", n).unwrap(),
401            Literal::Float(n) => write!(self.out, "{}", format_float(*n)).unwrap(),
402            Literal::Str(s) => write!(self.out, "\"{}\"", escape(s)).unwrap(),
403            Literal::Bytes(b) => {
404                write!(self.out, "b\"").unwrap();
405                for &c in b {
406                    if c.is_ascii() && (c as char).is_ascii_graphic() && c != b'"' && c != b'\\' {
407                        self.out.push(c as char);
408                    } else {
409                        write!(self.out, "\\x{:02x}", c).unwrap();
410                    }
411                }
412                write!(self.out, "\"").unwrap();
413            }
414            Literal::Bool(b) => write!(self.out, "{}", b).unwrap(),
415            Literal::Unit => write!(self.out, "()").unwrap(),
416        }
417    }
418
419    fn pattern(&mut self, p: &Pattern) {
420        match p {
421            Pattern::Lit(l) => self.literal(l),
422            Pattern::Var(n) => { write!(self.out, "{}", n).unwrap(); }
423            Pattern::Wild => { write!(self.out, "_").unwrap(); }
424            Pattern::Constructor { name, args } => {
425                write!(self.out, "{}", name).unwrap();
426                if !args.is_empty() {
427                    write!(self.out, "(").unwrap();
428                    for (i, a) in args.iter().enumerate() {
429                        if i > 0 { write!(self.out, ", ").unwrap(); }
430                        self.pattern(a);
431                    }
432                    write!(self.out, ")").unwrap();
433                }
434            }
435            Pattern::Record { fields, rest: _ } => {
436                write!(self.out, "{{ ").unwrap();
437                for (i, f) in fields.iter().enumerate() {
438                    if i > 0 { write!(self.out, ", ").unwrap(); }
439                    write!(self.out, "{}", f.name).unwrap();
440                    if let Some(p) = &f.pattern {
441                        write!(self.out, ": ").unwrap();
442                        self.pattern(p);
443                    }
444                }
445                write!(self.out, " }}").unwrap();
446            }
447            Pattern::Tuple(items) => {
448                write!(self.out, "(").unwrap();
449                for (i, it) in items.iter().enumerate() {
450                    if i > 0 { write!(self.out, ", ").unwrap(); }
451                    self.pattern(it);
452                }
453                write!(self.out, ")").unwrap();
454            }
455        }
456    }
457}
458
459fn escape(s: &str) -> String {
460    let mut out = String::with_capacity(s.len());
461    for c in s.chars() {
462        match c {
463            '\\' => out.push_str("\\\\"),
464            '"' => out.push_str("\\\""),
465            '\n' => out.push_str("\\n"),
466            '\t' => out.push_str("\\t"),
467            '\r' => out.push_str("\\r"),
468            c => out.push(c),
469        }
470    }
471    out
472}
473
474fn format_float(n: f64) -> String {
475    if n.is_finite() && n == n.trunc() {
476        format!("{:.1}", n)
477    } else {
478        format!("{}", n)
479    }
480}