Skip to main content

gruel_fmt/
emit.rs

1//! AST walker that emits canonical Gruel text into a [`Printer`].
2//!
3//! Subsequent ADR-0093 phases extend the set of nodes handled here:
4//! - Phase 1: smallest case (covered in `lib.rs` baseline).
5//! - Phase 2: expressions, statements, patterns, types.
6//! - Phase 3: top-level items (Function, StructDecl, EnumDecl, …).
7//! - Phase 4: comment / blank-line weaving via a trivia table.
8//!
9//! Exhaustive `match`es are deliberate: a new AST node kind triggers a
10//! compile-time error here rather than silently re-emitting nothing.
11
12use gruel_parser::ast::{
13    AnonFnExpr, AnonStructField, ArgMode, ArrayLitExpr, AssignStatement, AssignTarget,
14    AssocFnCallExpr, Ast, BinaryExpr, BinaryOp, BlockExpr, BoolLit, BreakExpr, CallArg, CallExpr,
15    CharLit, CheckedBlockExpr, ComptimeBlockExpr, ComptimeUnrollForExpr, ConstDecl, ContinueExpr,
16    DeriveDecl, Directive, DirectiveArg, EnumDecl, EnumStructLitExpr, EnumVariant,
17    EnumVariantField, EnumVariantKind, Expr, ExternFn, FieldDecl, FieldExpr, FieldInit,
18    FieldPattern, FloatLit, ForExpr, Function, IfExpr, IndexExpr, IntLit, InterfaceDecl,
19    IntrinsicArg, IntrinsicCallExpr, Item, LetStatement, LinkExternBlock, LinkMode, LoopExpr,
20    MatchArm, MatchExpr, Method, MethodCallExpr, MethodSig, NegIntLit, Param, ParamMode, ParenExpr,
21    PathExpr, PathPattern, Pattern, RangeExpr, ReturnExpr, SelfParam, SelfReceiverKind, Statement,
22    StringLit, StructDecl, StructLitExpr, TupleElemPattern, TupleExpr, TupleIndexExpr, TypeExpr,
23    TypeLitExpr, UnaryExpr, UnaryOp, UnitLit, Visibility, WhileExpr,
24};
25use gruel_util::Span;
26
27use crate::Printer;
28
29fn item_span(item: &Item) -> Span {
30    match item {
31        Item::Function(f) => f.span,
32        Item::Struct(s) => s.span,
33        Item::Enum(e) => e.span,
34        Item::Interface(i) => i.span,
35        Item::Derive(d) => d.span,
36        Item::Const(c) => c.span,
37        Item::LinkExtern(b) => b.span,
38        Item::Error(s) => *s,
39    }
40}
41
42fn statement_span(s: &Statement) -> Span {
43    match s {
44        Statement::Let(l) => l.span,
45        Statement::Assign(a) => a.span,
46        Statement::Expr(e) => e.span(),
47    }
48}
49
50// ---------- top-level walkers ----------
51
52pub fn emit_ast(p: &mut Printer<'_>, ast: &Ast) {
53    if let Some(doc) = &ast.module_doc {
54        // Module-level doc lives at the very top.
55        emit_doc(p, doc);
56    }
57    for (i, item) in ast.items.iter().enumerate() {
58        let span = item_span(item);
59        if i > 0 || ast.module_doc.is_some() {
60            p.blank_line();
61        }
62        // Drain `// comments` and blank-line runs preceding this item.
63        p.drain_trivia_before(span.start);
64        emit_item(p, item);
65        p.mark_emitted_end(span.end);
66    }
67    // Flush any trailing trivia past the last item (file-trailing comments).
68    p.drain_trivia_remaining();
69}
70
71pub fn emit_item(p: &mut Printer<'_>, item: &Item) {
72    match item {
73        Item::Function(f) => emit_function(p, f),
74        Item::Struct(s) => emit_struct_decl(p, s),
75        Item::Enum(e) => emit_enum_decl(p, e),
76        Item::Interface(i) => emit_interface_decl(p, i),
77        Item::Derive(d) => emit_derive_decl(p, d),
78        Item::Const(c) => emit_const_decl(p, c),
79        Item::LinkExtern(b) => emit_link_extern(p, b),
80        Item::Error(_) => panic!("gruel-fmt: Item::Error in successfully-parsed AST"),
81    }
82}
83
84// ---------- top-level items (Phase 3) ----------
85
86fn emit_doc(p: &mut Printer<'_>, doc: &gruel_parser::ast::Doc) {
87    for line in doc.body.split('\n') {
88        if line.is_empty() {
89            p.write_str("///");
90        } else {
91            p.write_str("/// ");
92            p.write_str(line);
93        }
94        p.newline();
95    }
96}
97
98fn emit_directives(p: &mut Printer<'_>, dirs: &[Directive]) {
99    for dir in dirs {
100        emit_directive(p, dir);
101        p.newline();
102    }
103}
104
105fn emit_directive(p: &mut Printer<'_>, dir: &Directive) {
106    p.write_str("@");
107    p.write_ident(dir.name.name);
108    if !dir.args.is_empty() {
109        p.write_str("(");
110        for (i, arg) in dir.args.iter().enumerate() {
111            if i > 0 {
112                p.write_str(", ");
113            }
114            emit_directive_arg(p, arg);
115        }
116        p.write_str(")");
117    }
118}
119
120fn emit_directive_arg(p: &mut Printer<'_>, arg: &DirectiveArg) {
121    match arg {
122        DirectiveArg::Ident(ident) => p.write_ident(ident.name),
123        DirectiveArg::String(lit) => emit_string_literal(p, lit),
124    }
125}
126
127fn emit_visibility(p: &mut Printer<'_>, vis: Visibility) {
128    if matches!(vis, Visibility::Public) {
129        p.write_str("pub ");
130    }
131}
132
133fn emit_function(p: &mut Printer<'_>, f: &Function) {
134    if let Some(doc) = &f.doc {
135        emit_doc(p, doc);
136    }
137    emit_directives(p, &f.directives);
138    emit_visibility(p, f.visibility);
139    // `is_unchecked` is derived from `@mark(unchecked)` in `directives` — no
140    // standalone keyword to emit here.
141    p.write_str("fn ");
142    p.write_ident(f.name.name);
143    emit_param_list(p, &f.params);
144    if let Some(ret) = &f.return_type {
145        p.write_str(" -> ");
146        emit_type_expr(p, ret);
147    }
148    p.write_str(" ");
149    if let Expr::Block(block) = &f.body {
150        emit_block(p, block);
151    } else {
152        // Function bodies are always blocks per the grammar; if a non-block
153        // ever appears, the formatter notices loudly.
154        panic!("gruel-fmt: Function body is not a BlockExpr");
155    }
156    p.newline();
157}
158
159fn emit_struct_decl(p: &mut Printer<'_>, s: &StructDecl) {
160    if let Some(doc) = &s.doc {
161        emit_doc(p, doc);
162    }
163    emit_directives(p, &s.directives);
164    emit_visibility(p, s.visibility);
165    p.write_str("struct ");
166    p.write_ident(s.name.name);
167    p.write_str(" {");
168    if s.fields.is_empty() && s.methods.is_empty() {
169        p.drain_trivia_before(s.span.end);
170        p.write_str("}");
171    } else {
172        p.newline();
173        p.indent();
174        for field in &s.fields {
175            p.drain_trivia_before(field.span.start);
176            emit_field_decl(p, field);
177            p.mark_emitted_end(field.span.end);
178            p.write_str(",");
179            p.drain_trailing_comment_on_line();
180            p.newline();
181        }
182        if !s.fields.is_empty() && !s.methods.is_empty() {
183            p.blank_line();
184        }
185        for (i, m) in s.methods.iter().enumerate() {
186            if i > 0 {
187                p.blank_line();
188            }
189            p.drain_trivia_before(m.span.start);
190            emit_method(p, m);
191            p.mark_emitted_end(m.span.end);
192            p.newline();
193        }
194        p.drain_trivia_before(s.span.end);
195        p.dedent();
196        p.write_str("}");
197    }
198    p.newline();
199}
200
201fn emit_field_decl(p: &mut Printer<'_>, field: &FieldDecl) {
202    if let Some(doc) = &field.doc {
203        emit_doc(p, doc);
204    }
205    emit_visibility(p, field.visibility);
206    p.write_ident(field.name.name);
207    p.write_str(": ");
208    emit_type_expr(p, &field.ty);
209}
210
211fn emit_enum_decl(p: &mut Printer<'_>, e: &EnumDecl) {
212    if let Some(doc) = &e.doc {
213        emit_doc(p, doc);
214    }
215    emit_directives(p, &e.directives);
216    emit_visibility(p, e.visibility);
217    p.write_str("enum ");
218    p.write_ident(e.name.name);
219    p.write_str(" {");
220    if e.variants.is_empty() && e.methods.is_empty() {
221        p.write_str("}");
222    } else {
223        p.newline();
224        p.indent();
225        for variant in &e.variants {
226            emit_enum_variant(p, variant);
227            p.write_str(",");
228            p.newline();
229        }
230        if !e.variants.is_empty() && !e.methods.is_empty() {
231            p.blank_line();
232        }
233        for (i, m) in e.methods.iter().enumerate() {
234            if i > 0 {
235                p.blank_line();
236            }
237            emit_method(p, m);
238            p.newline();
239        }
240        p.dedent();
241        p.write_str("}");
242    }
243    p.newline();
244}
245
246fn emit_enum_variant(p: &mut Printer<'_>, v: &EnumVariant) {
247    if let Some(doc) = &v.doc {
248        emit_doc(p, doc);
249    }
250    p.write_ident(v.name.name);
251    match &v.kind {
252        EnumVariantKind::Unit => {}
253        EnumVariantKind::Tuple(tys) => {
254            p.write_str("(");
255            for (i, ty) in tys.iter().enumerate() {
256                if i > 0 {
257                    p.write_str(", ");
258                }
259                emit_type_expr(p, ty);
260            }
261            p.write_str(")");
262        }
263        EnumVariantKind::Struct(fields) => {
264            p.write_str(" {");
265            if fields.is_empty() {
266                p.write_str("}");
267            } else {
268                p.newline();
269                p.indent();
270                for f in fields {
271                    emit_enum_variant_field(p, f);
272                    p.write_str(",");
273                    p.newline();
274                }
275                p.dedent();
276                p.write_str("}");
277            }
278        }
279    }
280}
281
282fn emit_enum_variant_field(p: &mut Printer<'_>, f: &EnumVariantField) {
283    if let Some(doc) = &f.doc {
284        emit_doc(p, doc);
285    }
286    emit_visibility(p, f.visibility);
287    p.write_ident(f.name.name);
288    p.write_str(": ");
289    emit_type_expr(p, &f.ty);
290}
291
292fn emit_interface_decl(p: &mut Printer<'_>, i: &InterfaceDecl) {
293    if let Some(doc) = &i.doc {
294        emit_doc(p, doc);
295    }
296    emit_directives(p, &i.directives);
297    emit_visibility(p, i.visibility);
298    p.write_str("interface ");
299    p.write_ident(i.name.name);
300    p.write_str(" {");
301    if i.methods.is_empty() {
302        p.write_str("}");
303    } else {
304        p.newline();
305        p.indent();
306        for (idx, sig) in i.methods.iter().enumerate() {
307            if idx > 0 {
308                p.newline();
309            }
310            emit_method_sig(p, sig);
311            p.newline();
312        }
313        p.dedent();
314        p.write_str("}");
315    }
316    p.newline();
317}
318
319fn emit_method_sig(p: &mut Printer<'_>, sig: &MethodSig) {
320    if let Some(doc) = &sig.doc {
321        emit_doc(p, doc);
322    }
323    emit_directives(p, &sig.directives);
324    // `is_unchecked` reflects `@mark(unchecked)` in `directives`.
325    p.write_str("fn ");
326    p.write_ident(sig.name.name);
327    p.write_str("(");
328    emit_self_param(p, &sig.receiver);
329    for param in &sig.params {
330        p.write_str(", ");
331        emit_param(p, param);
332    }
333    p.write_str(")");
334    if let Some(ret) = &sig.return_type {
335        p.write_str(" -> ");
336        emit_type_expr(p, ret);
337    }
338    p.write_str(";");
339}
340
341fn emit_derive_decl(p: &mut Printer<'_>, d: &DeriveDecl) {
342    if let Some(doc) = &d.doc {
343        emit_doc(p, doc);
344    }
345    p.write_str("derive ");
346    p.write_ident(d.name.name);
347    p.write_str(" {");
348    if d.methods.is_empty() {
349        p.write_str("}");
350    } else {
351        p.newline();
352        p.indent();
353        for (idx, m) in d.methods.iter().enumerate() {
354            if idx > 0 {
355                p.blank_line();
356            }
357            emit_method(p, m);
358            p.newline();
359        }
360        p.dedent();
361        p.write_str("}");
362    }
363    p.newline();
364}
365
366fn emit_const_decl(p: &mut Printer<'_>, c: &ConstDecl) {
367    if let Some(doc) = &c.doc {
368        emit_doc(p, doc);
369    }
370    emit_directives(p, &c.directives);
371    emit_visibility(p, c.visibility);
372    p.write_str("const ");
373    p.write_ident(c.name.name);
374    if let Some(ty) = &c.ty {
375        p.write_str(": ");
376        emit_type_expr(p, ty);
377    }
378    p.write_str(" = ");
379    emit_expr(p, &c.init);
380    p.write_str(";");
381    p.newline();
382}
383
384fn emit_link_extern(p: &mut Printer<'_>, b: &LinkExternBlock) {
385    if let Some(doc) = &b.doc {
386        emit_doc(p, doc);
387    }
388    match b.link_mode {
389        LinkMode::Dynamic => p.write_str("link_extern("),
390        LinkMode::Static => p.write_str("static_link_extern("),
391    }
392    emit_string_literal(p, &b.library);
393    p.write_str(") {");
394    if b.items.is_empty() {
395        p.write_str("}");
396    } else {
397        p.newline();
398        p.indent();
399        for (idx, item) in b.items.iter().enumerate() {
400            if idx > 0 {
401                p.newline();
402            }
403            emit_extern_fn(p, item);
404            p.newline();
405        }
406        p.dedent();
407        p.write_str("}");
408    }
409    p.newline();
410}
411
412fn emit_extern_fn(p: &mut Printer<'_>, f: &ExternFn) {
413    if let Some(doc) = &f.doc {
414        emit_doc(p, doc);
415    }
416    emit_directives(p, &f.directives);
417    p.write_str("fn ");
418    p.write_ident(f.name.name);
419    emit_param_list(p, &f.params);
420    if let Some(ret) = &f.return_type {
421        p.write_str(" -> ");
422        emit_type_expr(p, ret);
423    }
424    p.write_str(";");
425}
426
427fn emit_method(p: &mut Printer<'_>, m: &Method) {
428    if let Some(doc) = &m.doc {
429        emit_doc(p, doc);
430    }
431    emit_directives(p, &m.directives);
432    emit_visibility(p, m.visibility);
433    // `is_unchecked` reflects `@mark(unchecked)` in `directives`.
434    p.write_str("fn ");
435    p.write_ident(m.name.name);
436    p.write_str("(");
437    let mut first = true;
438    if let Some(recv) = &m.receiver {
439        emit_self_param(p, recv);
440        first = false;
441    }
442    for param in &m.params {
443        if !first {
444            p.write_str(", ");
445        }
446        emit_param(p, param);
447        first = false;
448    }
449    p.write_str(")");
450    if let Some(ret) = &m.return_type {
451        p.write_str(" -> ");
452        emit_type_expr(p, ret);
453    }
454    p.write_str(" ");
455    if let Expr::Block(b) = &m.body {
456        emit_block(p, b);
457    } else {
458        panic!("gruel-fmt: Method body is not a BlockExpr");
459    }
460}
461
462fn emit_param_list(p: &mut Printer<'_>, params: &[Param]) {
463    p.write_str("(");
464    for (i, param) in params.iter().enumerate() {
465        if i > 0 {
466            p.write_str(", ");
467        }
468        emit_param(p, param);
469    }
470    p.write_str(")");
471}
472
473fn emit_param(p: &mut Printer<'_>, param: &Param) {
474    if param.is_comptime || matches!(param.mode, ParamMode::Comptime) {
475        p.write_str("comptime ");
476    }
477    p.write_ident(param.name.name);
478    p.write_str(": ");
479    emit_type_expr(p, &param.ty);
480}
481
482fn emit_self_param(p: &mut Printer<'_>, sp: &SelfParam) {
483    match sp.kind {
484        SelfReceiverKind::ByValue => p.write_str("self"),
485        SelfReceiverKind::Ref => p.write_str("self: Ref(Self)"),
486        SelfReceiverKind::MutRef => p.write_str("self: MutRef(Self)"),
487    }
488}
489
490// ---------- expressions (Phase 2) ----------
491
492pub fn emit_expr(p: &mut Printer<'_>, expr: &Expr) {
493    match expr {
494        Expr::Int(lit) => emit_int_literal(p, lit),
495        Expr::Float(lit) => emit_float_literal(p, lit),
496        Expr::String(lit) => emit_string_literal(p, lit),
497        Expr::Char(lit) => emit_char_literal(p, lit),
498        Expr::Bool(lit) => emit_bool_literal(p, lit),
499        Expr::Unit(lit) => emit_unit_literal(p, lit),
500        Expr::Ident(ident) => p.write_ident(ident.name),
501        Expr::SelfExpr(_) => p.write_str("self"),
502        Expr::Binary(b) => emit_binary(p, b),
503        Expr::Unary(u) => emit_unary(p, u),
504        Expr::Paren(par) => emit_paren(p, par),
505        Expr::Block(b) => emit_block(p, b),
506        Expr::If(if_expr) => emit_if(p, if_expr),
507        Expr::Match(m) => emit_match(p, m),
508        Expr::While(w) => emit_while(p, w),
509        Expr::For(f) => emit_for(p, f),
510        Expr::Loop(l) => emit_loop(p, l),
511        Expr::Call(c) => emit_call(p, c),
512        Expr::Break(b) => emit_break(p, b),
513        Expr::Continue(c) => emit_continue(p, c),
514        Expr::Return(r) => emit_return(p, r),
515        Expr::StructLit(s) => emit_struct_lit(p, s),
516        Expr::Field(f) => emit_field_expr(p, f),
517        Expr::MethodCall(mc) => emit_method_call(p, mc),
518        Expr::IntrinsicCall(ic) => emit_intrinsic_call(p, ic),
519        Expr::ArrayLit(a) => emit_array_lit(p, a),
520        Expr::Index(idx) => emit_index(p, idx),
521        Expr::Path(path) => emit_path(p, path),
522        Expr::EnumStructLit(e) => emit_enum_struct_lit(p, e),
523        Expr::AssocFnCall(a) => emit_assoc_fn_call(p, a),
524        Expr::Comptime(c) => emit_comptime_block(p, c),
525        Expr::ComptimeUnrollFor(c) => emit_comptime_unroll_for(p, c),
526        Expr::Checked(c) => emit_checked_block(p, c),
527        Expr::TypeLit(t) => emit_type_lit(p, t),
528        Expr::Tuple(t) => emit_tuple(p, t),
529        Expr::TupleIndex(t) => emit_tuple_index(p, t),
530        Expr::Range(r) => emit_range(p, r),
531        Expr::AnonFn(a) => emit_anon_fn(p, a),
532        Expr::Error(_) => panic!("gruel-fmt: Expr::Error in successfully-parsed AST"),
533    }
534}
535
536fn emit_int_literal(p: &mut Printer<'_>, lit: &IntLit) {
537    p.write_str(&lit.value.to_string());
538}
539
540fn emit_float_literal(p: &mut Printer<'_>, lit: &FloatLit) {
541    let v = f64::from_bits(lit.bits);
542    let s = format!("{}", v);
543    if s.contains('.')
544        || s.contains('e')
545        || s.contains('E')
546        || s.contains("inf")
547        || s.contains("NaN")
548    {
549        p.write_str(&s);
550    } else {
551        p.write_str(&s);
552        p.write_str(".0");
553    }
554}
555
556fn emit_string_literal(p: &mut Printer<'_>, lit: &StringLit) {
557    p.write_str("\"");
558    let s = p.resolve(lit.value).to_string();
559    for c in s.chars() {
560        match c {
561            '\\' => p.write_str("\\\\"),
562            '"' => p.write_str("\\\""),
563            '\n' => p.write_str("\\n"),
564            '\t' => p.write_str("\\t"),
565            '\r' => p.write_str("\\r"),
566            '\0' => p.write_str("\\0"),
567            c => {
568                let mut buf = [0u8; 4];
569                p.write_str(c.encode_utf8(&mut buf));
570            }
571        }
572    }
573    p.write_str("\"");
574}
575
576fn emit_char_literal(p: &mut Printer<'_>, lit: &CharLit) {
577    p.write_str("'");
578    match char::from_u32(lit.value) {
579        Some(c) => match c {
580            '\\' => p.write_str("\\\\"),
581            '\'' => p.write_str("\\'"),
582            '\n' => p.write_str("\\n"),
583            '\t' => p.write_str("\\t"),
584            '\r' => p.write_str("\\r"),
585            '\0' => p.write_str("\\0"),
586            c => {
587                let mut buf = [0u8; 4];
588                p.write_str(c.encode_utf8(&mut buf));
589            }
590        },
591        None => {
592            // Non-USV code point: emit a \u{...} escape. The lexer doesn't
593            // accept this today, but we keep the formatter total.
594            p.write_str(&format!("\\u{{{:X}}}", lit.value));
595        }
596    }
597    p.write_str("'");
598}
599
600fn emit_bool_literal(p: &mut Printer<'_>, lit: &BoolLit) {
601    p.write_str(if lit.value { "true" } else { "false" });
602}
603
604fn emit_unit_literal(p: &mut Printer<'_>, _lit: &UnitLit) {
605    p.write_str("()");
606}
607
608fn emit_binary(p: &mut Printer<'_>, b: &BinaryExpr) {
609    emit_expr(p, &b.left);
610    p.write_str(" ");
611    p.write_str(binary_op_str(b.op));
612    p.write_str(" ");
613    emit_expr(p, &b.right);
614}
615
616fn emit_unary(p: &mut Printer<'_>, u: &UnaryExpr) {
617    p.write_str(unary_op_str(u.op));
618    emit_expr(p, &u.operand);
619}
620
621fn emit_paren(p: &mut Printer<'_>, par: &ParenExpr) {
622    p.write_str("(");
623    emit_expr(p, &par.inner);
624    p.write_str(")");
625}
626
627pub fn emit_block(p: &mut Printer<'_>, block: &BlockExpr) {
628    p.write_str("{");
629    if block.statements.is_empty() && matches!(*block.expr, Expr::Unit(_)) {
630        // Empty block: still drain trivia (`{ // comment }` keeps the
631        // comment) so it doesn't escape into surrounding context.
632        p.drain_trivia_before(block.span.end);
633        p.write_str("}");
634        return;
635    }
636    p.newline();
637    p.indent();
638    for stmt in &block.statements {
639        let span = statement_span(stmt);
640        p.drain_trivia_before(span.start);
641        emit_statement(p, stmt);
642        p.mark_emitted_end(span.end);
643        p.drain_trailing_comment_on_line();
644        p.newline();
645    }
646    if !matches!(*block.expr, Expr::Unit(_)) {
647        let span = block.expr.span();
648        p.drain_trivia_before(span.start);
649        emit_expr(p, &block.expr);
650        p.mark_emitted_end(span.end);
651        p.drain_trailing_comment_on_line();
652        p.newline();
653    }
654    // Drain any trailing trivia inside this block (e.g. comment just before `}`).
655    p.drain_trivia_before(block.span.end);
656    p.dedent();
657    p.write_str("}");
658}
659
660fn emit_if(p: &mut Printer<'_>, e: &IfExpr) {
661    if e.is_comptime {
662        p.write_str("comptime ");
663    }
664    p.write_str("if ");
665    emit_expr(p, &e.cond);
666    p.write_str(" ");
667    emit_block(p, &e.then_block);
668    if let Some(else_block) = &e.else_block {
669        p.write_str(" else ");
670        // `else if` collapses if the else block is exactly one if-expression
671        // with no other statements.
672        if else_block.statements.is_empty()
673            && let Expr::If(inner) = &*else_block.expr
674        {
675            emit_if(p, inner);
676            return;
677        }
678        emit_block(p, else_block);
679    }
680}
681
682fn emit_match(p: &mut Printer<'_>, m: &MatchExpr) {
683    p.write_str("match ");
684    emit_expr(p, &m.scrutinee);
685    p.write_str(" {");
686    if m.arms.is_empty() {
687        p.write_str("}");
688        return;
689    }
690    p.newline();
691    p.indent();
692    for arm in &m.arms {
693        emit_match_arm(p, arm);
694        p.write_str(",");
695        p.newline();
696    }
697    p.dedent();
698    p.write_str("}");
699}
700
701fn emit_match_arm(p: &mut Printer<'_>, arm: &MatchArm) {
702    emit_pattern(p, &arm.pattern);
703    // ADR-0079: a `comptime_unroll for` arm pattern is followed directly by
704    // its body block — no `=>` between them. Every other pattern uses
705    // `pat => body`.
706    match &arm.pattern {
707        Pattern::ComptimeUnrollArm { .. } => p.write_str(" "),
708        _ => p.write_str(" => "),
709    }
710    emit_expr(p, &arm.body);
711}
712
713fn emit_while(p: &mut Printer<'_>, w: &WhileExpr) {
714    p.write_str("while ");
715    emit_expr(p, &w.cond);
716    p.write_str(" ");
717    emit_block(p, &w.body);
718}
719
720fn emit_for(p: &mut Printer<'_>, f: &ForExpr) {
721    p.write_str("for ");
722    if f.is_mut {
723        p.write_str("mut ");
724    }
725    p.write_ident(f.binding.name);
726    p.write_str(" in ");
727    emit_expr(p, &f.iterable);
728    p.write_str(" ");
729    emit_block(p, &f.body);
730}
731
732fn emit_loop(p: &mut Printer<'_>, l: &LoopExpr) {
733    p.write_str("loop ");
734    emit_block(p, &l.body);
735}
736
737fn emit_call(p: &mut Printer<'_>, c: &CallExpr) {
738    p.write_ident(c.name.name);
739    emit_call_args(p, &c.args);
740}
741
742fn emit_call_args(p: &mut Printer<'_>, args: &[CallArg]) {
743    p.write_str("(");
744    for (i, arg) in args.iter().enumerate() {
745        if i > 0 {
746            p.write_str(", ");
747        }
748        match arg.mode {
749            ArgMode::Normal => {}
750        }
751        emit_expr(p, &arg.expr);
752    }
753    p.write_str(")");
754}
755
756fn emit_break(p: &mut Printer<'_>, _b: &BreakExpr) {
757    p.write_str("break");
758}
759
760fn emit_continue(p: &mut Printer<'_>, _c: &ContinueExpr) {
761    p.write_str("continue");
762}
763
764fn emit_return(p: &mut Printer<'_>, r: &ReturnExpr) {
765    p.write_str("return");
766    if let Some(value) = &r.value {
767        p.write_str(" ");
768        emit_expr(p, value);
769    }
770}
771
772fn emit_struct_lit(p: &mut Printer<'_>, s: &StructLitExpr) {
773    if let Some(base) = &s.base {
774        emit_expr(p, base);
775        p.write_str(".");
776    }
777    p.write_ident(s.name.name);
778    emit_field_inits(p, &s.fields);
779}
780
781fn emit_field_inits(p: &mut Printer<'_>, fields: &[FieldInit]) {
782    if fields.is_empty() {
783        p.write_str(" {}");
784        return;
785    }
786    p.write_str(" { ");
787    for (i, f) in fields.iter().enumerate() {
788        if i > 0 {
789            p.write_str(", ");
790        }
791        emit_field_init(p, f);
792    }
793    p.write_str(" }");
794}
795
796fn emit_field_init(p: &mut Printer<'_>, f: &FieldInit) {
797    p.write_ident(f.name.name);
798    p.write_str(": ");
799    emit_expr(p, &f.value);
800}
801
802fn emit_field_expr(p: &mut Printer<'_>, f: &FieldExpr) {
803    emit_expr(p, &f.base);
804    p.write_str(".");
805    p.write_ident(f.field.name);
806}
807
808fn emit_method_call(p: &mut Printer<'_>, m: &MethodCallExpr) {
809    emit_expr(p, &m.receiver);
810    p.write_str(".");
811    p.write_ident(m.method.name);
812    emit_call_args(p, &m.args);
813}
814
815fn emit_intrinsic_call(p: &mut Printer<'_>, c: &IntrinsicCallExpr) {
816    p.write_str("@");
817    p.write_ident(c.name.name);
818    p.write_str("(");
819    for (i, arg) in c.args.iter().enumerate() {
820        if i > 0 {
821            p.write_str(", ");
822        }
823        match arg {
824            IntrinsicArg::Expr(e) => emit_expr(p, e),
825            IntrinsicArg::Type(t) => emit_type_expr(p, t),
826        }
827    }
828    p.write_str(")");
829}
830
831fn emit_array_lit(p: &mut Printer<'_>, a: &ArrayLitExpr) {
832    p.write_str("[");
833    for (i, elem) in a.elements.iter().enumerate() {
834        if i > 0 {
835            p.write_str(", ");
836        }
837        emit_expr(p, elem);
838    }
839    p.write_str("]");
840}
841
842fn emit_index(p: &mut Printer<'_>, i: &IndexExpr) {
843    emit_expr(p, &i.base);
844    p.write_str("[");
845    emit_expr(p, &i.index);
846    p.write_str("]");
847}
848
849fn emit_path(p: &mut Printer<'_>, path: &PathExpr) {
850    if let Some(base) = &path.base {
851        emit_expr(p, base);
852        p.write_str(".");
853    }
854    p.write_ident(path.type_name.name);
855    p.write_str("::");
856    p.write_ident(path.variant.name);
857}
858
859fn emit_enum_struct_lit(p: &mut Printer<'_>, e: &EnumStructLitExpr) {
860    if let Some(base) = &e.base {
861        emit_expr(p, base);
862        p.write_str(".");
863    }
864    p.write_ident(e.type_name.name);
865    p.write_str("::");
866    p.write_ident(e.variant.name);
867    emit_field_inits(p, &e.fields);
868}
869
870fn emit_assoc_fn_call(p: &mut Printer<'_>, a: &AssocFnCallExpr) {
871    if let Some(base) = &a.base {
872        emit_expr(p, base);
873        p.write_str(".");
874    }
875    p.write_ident(a.type_name.name);
876    if !a.type_args.is_empty() {
877        p.write_str("(");
878        for (i, t) in a.type_args.iter().enumerate() {
879            if i > 0 {
880                p.write_str(", ");
881            }
882            emit_expr(p, t);
883        }
884        p.write_str(")");
885    }
886    p.write_str("::");
887    p.write_ident(a.function.name);
888    emit_call_args(p, &a.args);
889}
890
891fn emit_comptime_block(p: &mut Printer<'_>, c: &ComptimeBlockExpr) {
892    p.write_str("comptime ");
893    if let Expr::Block(b) = &*c.expr {
894        emit_block(p, b);
895    } else {
896        // `comptime <expr>` syntax — emit verbatim.
897        emit_expr(p, &c.expr);
898    }
899}
900
901fn emit_comptime_unroll_for(p: &mut Printer<'_>, c: &ComptimeUnrollForExpr) {
902    p.write_str("comptime_unroll for ");
903    p.write_ident(c.binding.name);
904    p.write_str(" in ");
905    emit_expr(p, &c.iterable);
906    p.write_str(" ");
907    emit_block(p, &c.body);
908}
909
910fn emit_checked_block(p: &mut Printer<'_>, c: &CheckedBlockExpr) {
911    p.write_str("checked ");
912    if let Expr::Block(b) = &*c.expr {
913        emit_block(p, b);
914    } else {
915        emit_expr(p, &c.expr);
916    }
917}
918
919fn emit_type_lit(p: &mut Printer<'_>, t: &TypeLitExpr) {
920    emit_type_expr(p, &t.type_expr);
921}
922
923fn emit_tuple(p: &mut Printer<'_>, t: &TupleExpr) {
924    p.write_str("(");
925    for (i, elem) in t.elems.iter().enumerate() {
926        if i > 0 {
927            p.write_str(", ");
928        }
929        emit_expr(p, elem);
930    }
931    if t.elems.len() == 1 {
932        // 1-tuples require a trailing comma to disambiguate from parens.
933        p.write_str(",");
934    }
935    p.write_str(")");
936}
937
938fn emit_tuple_index(p: &mut Printer<'_>, t: &TupleIndexExpr) {
939    emit_expr(p, &t.base);
940    p.write_str(".");
941    p.write_str(&t.index.to_string());
942}
943
944fn emit_range(p: &mut Printer<'_>, r: &RangeExpr) {
945    if let Some(lo) = &r.lo {
946        emit_expr(p, lo);
947    }
948    p.write_str("..");
949    if let Some(hi) = &r.hi {
950        emit_expr(p, hi);
951    }
952}
953
954fn emit_anon_fn(p: &mut Printer<'_>, a: &AnonFnExpr) {
955    p.write_str("fn");
956    emit_param_list(p, &a.params);
957    if let Some(ret) = &a.return_type {
958        p.write_str(" -> ");
959        emit_type_expr(p, ret);
960    }
961    p.write_str(" ");
962    emit_block(p, &a.body);
963}
964
965// ---------- statements (Phase 2) ----------
966
967pub fn emit_statement(p: &mut Printer<'_>, stmt: &Statement) {
968    match stmt {
969        Statement::Let(l) => emit_let(p, l),
970        Statement::Assign(a) => emit_assign(p, a),
971        Statement::Expr(e) => {
972            emit_expr(p, e);
973            // Block-like expressions used as statements don't carry a `;`
974            // (matches rustfmt convention: `while {} ;` collapses to
975            // `while {}`).
976            if !is_block_like_expr(e) {
977                p.write_str(";");
978            }
979        }
980    }
981}
982
983fn is_block_like_expr(e: &Expr) -> bool {
984    // Only expressions the parser accepts as bare statements (no trailing
985    // `;` required between this expr and the next statement). `checked`,
986    // `comptime`, and `comptime_unroll for` are *expressions*, not
987    // statements — they still need a semicolon when used in statement
988    // position.
989    matches!(
990        e,
991        Expr::Block(_)
992            | Expr::If(_)
993            | Expr::Match(_)
994            | Expr::While(_)
995            | Expr::For(_)
996            | Expr::Loop(_)
997    )
998}
999
1000fn emit_let(p: &mut Printer<'_>, l: &LetStatement) {
1001    emit_directives(p, &l.directives);
1002    p.write_str("let ");
1003    if let Pattern::Ident { name, .. } = &l.pattern {
1004        if l.is_mut {
1005            p.write_str("mut ");
1006        }
1007        p.write_ident(name.name);
1008    } else {
1009        emit_pattern(p, &l.pattern);
1010    }
1011    if let Some(ty) = &l.ty {
1012        p.write_str(": ");
1013        emit_type_expr(p, ty);
1014    }
1015    p.write_str(" = ");
1016    emit_expr(p, &l.init);
1017    p.write_str(";");
1018}
1019
1020fn emit_assign(p: &mut Printer<'_>, a: &AssignStatement) {
1021    emit_assign_target(p, &a.target);
1022    p.write_str(" = ");
1023    emit_expr(p, &a.value);
1024    p.write_str(";");
1025}
1026
1027fn emit_assign_target(p: &mut Printer<'_>, target: &AssignTarget) {
1028    match target {
1029        AssignTarget::Var(ident) => p.write_ident(ident.name),
1030        AssignTarget::Field(f) => emit_field_expr(p, f),
1031        AssignTarget::Index(i) => emit_index(p, i),
1032    }
1033}
1034
1035// ---------- patterns (Phase 2) ----------
1036
1037pub fn emit_pattern(p: &mut Printer<'_>, pat: &Pattern) {
1038    match pat {
1039        Pattern::Wildcard(_) => p.write_str("_"),
1040        Pattern::Ident { is_mut, name, .. } => {
1041            if *is_mut {
1042                p.write_str("mut ");
1043            }
1044            p.write_ident(name.name);
1045        }
1046        Pattern::Int(lit) => p.write_str(&lit.value.to_string()),
1047        Pattern::NegInt(NegIntLit { value, .. }) => {
1048            p.write_str("-");
1049            p.write_str(&value.to_string());
1050        }
1051        Pattern::Bool(BoolLit { value, .. }) => {
1052            p.write_str(if *value { "true" } else { "false" });
1053        }
1054        Pattern::Path(pp) => emit_path_pattern(p, pp),
1055        Pattern::DataVariant {
1056            base,
1057            type_name,
1058            variant,
1059            fields,
1060            ..
1061        } => {
1062            if let Some(b) = base {
1063                emit_expr(p, b);
1064                p.write_str(".");
1065            }
1066            p.write_ident(type_name.name);
1067            p.write_str("::");
1068            p.write_ident(variant.name);
1069            p.write_str("(");
1070            for (i, fld) in fields.iter().enumerate() {
1071                if i > 0 {
1072                    p.write_str(", ");
1073                }
1074                emit_tuple_elem_pattern(p, fld);
1075            }
1076            p.write_str(")");
1077        }
1078        Pattern::StructVariant {
1079            base,
1080            type_name,
1081            variant,
1082            fields,
1083            ..
1084        } => {
1085            if let Some(b) = base {
1086                emit_expr(p, b);
1087                p.write_str(".");
1088            }
1089            p.write_ident(type_name.name);
1090            p.write_str("::");
1091            p.write_ident(variant.name);
1092            emit_field_patterns(p, fields);
1093        }
1094        Pattern::Struct {
1095            type_name, fields, ..
1096        } => {
1097            p.write_ident(type_name.name);
1098            emit_field_patterns(p, fields);
1099        }
1100        Pattern::Tuple { elems, .. } => {
1101            p.write_str("(");
1102            for (i, e) in elems.iter().enumerate() {
1103                if i > 0 {
1104                    p.write_str(", ");
1105                }
1106                emit_tuple_elem_pattern(p, e);
1107            }
1108            if elems.len() == 1 {
1109                p.write_str(",");
1110            }
1111            p.write_str(")");
1112        }
1113        Pattern::ComptimeUnrollArm {
1114            binding, iterable, ..
1115        } => {
1116            p.write_str("comptime_unroll for ");
1117            p.write_ident(binding.name);
1118            p.write_str(" in ");
1119            emit_expr(p, iterable);
1120        }
1121    }
1122}
1123
1124fn emit_path_pattern(p: &mut Printer<'_>, pp: &PathPattern) {
1125    if let Some(base) = &pp.base {
1126        emit_expr(p, base);
1127        p.write_str(".");
1128    }
1129    p.write_ident(pp.type_name.name);
1130    p.write_str("::");
1131    p.write_ident(pp.variant.name);
1132}
1133
1134fn emit_field_patterns(p: &mut Printer<'_>, fields: &[FieldPattern]) {
1135    if fields.is_empty() {
1136        p.write_str(" {}");
1137        return;
1138    }
1139    p.write_str(" { ");
1140    for (i, fp) in fields.iter().enumerate() {
1141        if i > 0 {
1142            p.write_str(", ");
1143        }
1144        emit_field_pattern(p, fp);
1145    }
1146    p.write_str(" }");
1147}
1148
1149fn emit_field_pattern(p: &mut Printer<'_>, fp: &FieldPattern) {
1150    match &fp.field_name {
1151        None => p.write_str(".."),
1152        Some(name) => match &fp.sub {
1153            None => {
1154                if fp.is_mut {
1155                    p.write_str("mut ");
1156                }
1157                p.write_ident(name.name);
1158            }
1159            Some(sub) => {
1160                p.write_ident(name.name);
1161                p.write_str(": ");
1162                emit_pattern(p, sub);
1163            }
1164        },
1165    }
1166}
1167
1168fn emit_tuple_elem_pattern(p: &mut Printer<'_>, te: &TupleElemPattern) {
1169    match te {
1170        TupleElemPattern::Pattern(pat) => emit_pattern(p, pat),
1171        TupleElemPattern::Rest(_) => p.write_str(".."),
1172    }
1173}
1174
1175// ---------- type expressions ----------
1176
1177pub fn emit_type_expr(p: &mut Printer<'_>, ty: &TypeExpr) {
1178    match ty {
1179        TypeExpr::Named(ident) => p.write_ident(ident.name),
1180        TypeExpr::Unit(_) => p.write_str("()"),
1181        TypeExpr::Never(_) => p.write_str("!"),
1182        TypeExpr::Array {
1183            element, length, ..
1184        } => {
1185            p.write_str("[");
1186            emit_type_expr(p, element);
1187            p.write_str("; ");
1188            p.write_str(&length.to_string());
1189            p.write_str("]");
1190        }
1191        TypeExpr::AnonymousStruct {
1192            directives,
1193            fields,
1194            methods,
1195            ..
1196        } => {
1197            emit_directives_inline(p, directives);
1198            p.write_str("struct {");
1199            if fields.is_empty() && methods.is_empty() {
1200                p.write_str("}");
1201            } else {
1202                p.newline();
1203                p.indent();
1204                for f in fields {
1205                    emit_anon_struct_field(p, f);
1206                    p.write_str(",");
1207                    p.newline();
1208                }
1209                if !fields.is_empty() && !methods.is_empty() {
1210                    p.blank_line();
1211                }
1212                for (i, m) in methods.iter().enumerate() {
1213                    if i > 0 {
1214                        p.blank_line();
1215                    }
1216                    emit_method(p, m);
1217                    p.newline();
1218                }
1219                p.dedent();
1220                p.write_str("}");
1221            }
1222        }
1223        TypeExpr::AnonymousEnum {
1224            directives,
1225            variants,
1226            methods,
1227            ..
1228        } => {
1229            emit_directives_inline(p, directives);
1230            p.write_str("enum {");
1231            if variants.is_empty() && methods.is_empty() {
1232                p.write_str("}");
1233            } else {
1234                p.newline();
1235                p.indent();
1236                for v in variants {
1237                    emit_enum_variant(p, v);
1238                    p.write_str(",");
1239                    p.newline();
1240                }
1241                if !variants.is_empty() && !methods.is_empty() {
1242                    p.blank_line();
1243                }
1244                for (i, m) in methods.iter().enumerate() {
1245                    if i > 0 {
1246                        p.blank_line();
1247                    }
1248                    emit_method(p, m);
1249                    p.newline();
1250                }
1251                p.dedent();
1252                p.write_str("}");
1253            }
1254        }
1255        TypeExpr::AnonymousInterface { methods, .. } => {
1256            p.write_str("interface {");
1257            if methods.is_empty() {
1258                p.write_str("}");
1259            } else {
1260                p.newline();
1261                p.indent();
1262                for sig in methods {
1263                    emit_method_sig(p, sig);
1264                    p.newline();
1265                }
1266                p.dedent();
1267                p.write_str("}");
1268            }
1269        }
1270        TypeExpr::TypeCall { callee, args, .. } => {
1271            p.write_ident(callee.name);
1272            p.write_str("(");
1273            for (i, a) in args.iter().enumerate() {
1274                if i > 0 {
1275                    p.write_str(", ");
1276                }
1277                emit_type_expr(p, a);
1278            }
1279            p.write_str(")");
1280        }
1281        TypeExpr::Tuple { elems, .. } => {
1282            p.write_str("(");
1283            for (i, t) in elems.iter().enumerate() {
1284                if i > 0 {
1285                    p.write_str(", ");
1286                }
1287                emit_type_expr(p, t);
1288            }
1289            if elems.len() == 1 {
1290                p.write_str(",");
1291            }
1292            p.write_str(")");
1293        }
1294    }
1295}
1296
1297fn emit_directives_inline(p: &mut Printer<'_>, dirs: &[Directive]) {
1298    for dir in dirs {
1299        emit_directive(p, dir);
1300        p.write_str(" ");
1301    }
1302}
1303
1304fn emit_anon_struct_field(p: &mut Printer<'_>, f: &AnonStructField) {
1305    if let Some(doc) = &f.doc {
1306        emit_doc(p, doc);
1307    }
1308    p.write_ident(f.name.name);
1309    p.write_str(": ");
1310    emit_type_expr(p, &f.ty);
1311}
1312
1313// ---------- operator tables ----------
1314
1315fn binary_op_str(op: BinaryOp) -> &'static str {
1316    match op {
1317        BinaryOp::Add => "+",
1318        BinaryOp::Sub => "-",
1319        BinaryOp::Mul => "*",
1320        BinaryOp::Div => "/",
1321        BinaryOp::Mod => "%",
1322        BinaryOp::Eq => "==",
1323        BinaryOp::Ne => "!=",
1324        BinaryOp::Lt => "<",
1325        BinaryOp::Gt => ">",
1326        BinaryOp::Le => "<=",
1327        BinaryOp::Ge => ">=",
1328        BinaryOp::And => "&&",
1329        BinaryOp::Or => "||",
1330        BinaryOp::BitAnd => "&",
1331        BinaryOp::BitOr => "|",
1332        BinaryOp::BitXor => "^",
1333        BinaryOp::Shl => "<<",
1334        BinaryOp::Shr => ">>",
1335    }
1336}
1337
1338fn unary_op_str(op: UnaryOp) -> &'static str {
1339    match op {
1340        UnaryOp::Neg => "-",
1341        UnaryOp::Not => "!",
1342        UnaryOp::BitNot => "~",
1343        UnaryOp::Ref => "&",
1344        UnaryOp::MutRef => "&mut ",
1345    }
1346}